123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575 |
- # Created: 21.03.2011
- # Copyright (C) 2011, Manfred Moitzi
- # License: MIT License
- from typing import TYPE_CHECKING, Union, List, Iterable, Tuple, Dict, Hashable, Sequence, Optional
- from ezdxf.graphicsfactory import GraphicsFactory
- from ezdxf.entityspace import EntitySpace
- from ezdxf.query import EntityQuery
- from ezdxf.groupby import groupby
- from ezdxf.lldxf.const import STD_SCALES, DXFValueError
- if TYPE_CHECKING:
- from ezdxf.eztypes import Drawing, EntityDB, LayoutType, DXFEntity, TagWriter, DXFFactoryType, EntitySpace
- from ezdxf.eztypes import KeyFunc, GenericLayoutType
- class DXF12Layouts:
- """
- The Layout container.
- """
- def __init__(self, drawing: 'Drawing'):
- entities = drawing.sections.entities
- model_space = entities.model_space_entities()
- self._modelspace = DXF12Layout(model_space, drawing.dxffactory, 0)
- paper_space = entities.active_layout_entities()
- self._paperspace = DXF12Layout(paper_space, drawing.dxffactory, 1)
- entities.clear() # remove entities for entities section -> stored in layouts
- def __iter__(self) -> 'LayoutType':
- yield self._modelspace
- yield self._paperspace
- def __len__(self) -> int:
- return 2
- def modelspace(self) -> 'LayoutType':
- return self._modelspace
- def get(self, name: str = "") -> 'LayoutType':
- # AC1009 supports only one paperspace/layout
- return self._paperspace
- def names(self) -> List[str]:
- return []
- def get_layout_for_entity(self, entity: 'DXFEntity') -> 'LayoutType':
- # paperspace attribute defaults to 0 if not present
- if entity in self._modelspace:
- return self._modelspace
- elif entity in self._paperspace:
- return self._paperspace
- else:
- return None
- def active_layout(self) -> 'LayoutType':
- return self._paperspace
- def write_entities_section(self, tagwriter: 'TagWriter') -> None:
- self._modelspace.write(tagwriter)
- self._paperspace.write(tagwriter)
- class BaseLayout(GraphicsFactory):
- """
- Base class for DXF12Layout() and DXF12BlockLayout()
- Entities are wrapped into class GraphicEntity() or inherited.
- """
- def __init__(self, dxffactory: 'DXFFactoryType', entity_space: 'EnitySpace'):
- super(BaseLayout, self).__init__(dxffactory)
- self._entity_space = entity_space
- def __len__(self) -> int:
- """
- Entities count.
- """
- return len(self._entity_space)
- def __iter__(self) -> Iterable['DXFEntity']:
- """
- Iterate over all drawing entities in this layout.
- Returns: :class:`DXFEntity`
- """
- wrap = self._dxffactory.wrap_handle
- for handle in self._entity_space:
- yield wrap(handle)
- @property
- def entitydb(self) -> 'EnityDB':
- return self._dxffactory.entitydb
- @property
- def drawing(self) -> 'Drawing':
- return self._dxffactory.drawing
- def build_and_add_entity(self, type_: str, dxfattribs: dict) -> 'DXFEntity':
- """
- Create entity in drawing database and add entity to the entity space.
- Args:
- type_ (str): DXF type string, like 'LINE', 'CIRCLE' or 'LWPOLYLINE'
- dxfattribs (dict): DXF attributes for the new entity
- """
- entity = self.build_entity(type_, dxfattribs)
- self.add_entity(entity)
- return entity
- def build_entity(self, type_: str, dxfattribs: dict) -> 'DXFEntity':
- """
- Create entity in drawing database, returns a wrapper class inherited from GraphicEntity().
- Adds entity to the drawing database.
- Args:
- type_ (str): DXF type string, like 'LINE', 'CIRCLE' or 'LWPOLYLINE'
- dxfattribs(dict): DXF attributes for the new entity
- """
- entity = self._dxffactory.create_db_entry(type_, dxfattribs)
- self._set_paperspace(entity)
- return entity
- def add_entity(self, entity: 'DXFEntity') -> None:
- """
- Add an existing :class:`DXFEntity` to a layout, but be sure to unlink (:meth:`~Layout.unlink_entity()`) first the entity
- from the previous owner layout.
- """
- self._entity_space.append(entity.dxf.handle)
- self._set_paperspace(entity)
- for linked_entity in entity.linked_entities():
- self._set_paperspace(linked_entity)
- def unlink_entity(self, entity: 'DXFEntity') -> None:
- """
- Unlink `entity` from layout but does not delete entity from the drawing database.
- Removes `entity` just from entity space but not from the drawing database.
- Args:
- entity: :class:`DXFEntity`
- """
- self._entity_space.delete_entity(entity)
- entity.dxf.paperspace = -1 # set invalid paper space
- if entity.supports_dxf_attrib('owner'): # R2000
- entity.dxf.owner = '0'
- def delete_entity(self, entity: 'DXFEntity') -> None:
- """
- Delete `entity` from layout (entity space) and drawing database.
- Args:
- entity: :class:`DXFEntity`
- """
- self.entitydb.delete_entity(entity) # 1. delete from drawing database
- self.unlink_entity(entity) # 2. unlink from entity space
- def delete_all_entities(self) -> None:
- """
- Delete all entities from Layout (entity space) and from drawing database.
- """
- # noinspection PyTypeChecker
- for entity in list(self): # temp list, because delete modifies the base data structure of the iterator
- self.delete_entity(entity)
- def _set_paperspace(self, entity: 'DXFEntity') -> None:
- pass # only for DXF 2000 and later necessary
- def get_entity_by_handle(self, handle: str) -> 'DXFEntity':
- """
- Get entity by handle as GraphicEntity() or inherited.
- """
- return self._dxffactory.wrap_handle(handle)
- def query(self, query='*') -> EntityQuery:
- """
- Get all DXF entities matching the :ref:`entity query string`.
- Args:
- query: eintity query string
- Returns: :class:`EntityQuery`
- """
- return EntityQuery(iter(self), query)
- def groupby(self, dxfattrib: str = "", key: 'KeyFunc' = None) -> Dict[Hashable, List['DXFEntity']]:
- """
- Returns a dict of entity lists, where entities are grouped by a `dxfattrib` or a `key` function.
- Args:
- dxfattrib: grouping by DXF attribute like "layer"
- key: key function, which accepts a :class:`DXFEntity` as argument, returns grouping key of this entity or
- None to ignore this object. Reason for ignoring: a queried DXF attribute is not supported by this
- entity.
- """
- return groupby(iter(self), dxfattrib, key)
- def move_to_layout(self, entity: 'DXFEntity', layout: 'GenericLayoutType') -> None:
- """
- Move entity from block layout to another layout.
- Args:
- entity: DXF entity to move
- layout: any layout (model space, paper space, block)
- """
- if entity.dxf.handle in self._entity_space:
- self.unlink_entity(entity)
- layout.add_entity(entity)
- else:
- raise DXFValueError('Layout does not contain entity.')
- class DXF12Layout(BaseLayout):
- """
- Layout representation
- """
- def __init__(self, entityspace: 'EntitySpace', dxffactory: 'DXFFactoryType', paperspace: int = 0):
- super(DXF12Layout, self).__init__(dxffactory, entityspace)
- self._paperspace = paperspace
- # start of public interface
- def __contains__(self, entity: Union[str, 'DXFEntity']):
- """
- Test if the layout contains the drawing element `entity` (aka `in` operator).
- """
- if not hasattr(entity, 'dxf'): # entity is a handle and not a wrapper class
- entity = self.get_entity_by_handle(entity)
- return entity.dxf.paperspace == self._paperspace and entity.dxf.handle in self._entity_space
- # end of public interface
- def _set_paperspace(self, entity: 'DXFEntity'):
- entity.dxf.paperspace = self._paperspace
- def page_setup(self, size: Tuple[int, int] = (297, 210),
- margins: Tuple[int, int, int, int] = (0, 0, 0, 0),
- units: str = 'mm',
- offset: Tuple[int, int] = (0, 0),
- rotation: float = 0,
- scale: int = 16) -> None:
- if self._paperspace == 0:
- raise DXFTypeError("No paper setup for model space.")
- # remove existing viewports
- for viewport in self.viewports():
- self.delete_entity(viewport)
- if int(rotation) not in (0, 1, 2, 3):
- raise DXFValueError("valid rotation values: 0-3")
- if isinstance(scale, int):
- scale = STD_SCALES.get(scale, (1, 1))
- if scale[0] == 0:
- raise DXFValueError("scale numerator can't be 0.")
- if scale[1] == 0:
- raise DXFValueError("scale denominator can't be 0.")
- scale_factor = scale[1] / scale[0]
- # TODO: don't know how to set inch or mm mode in R12
- units = units.lower()
- if units.startswith('inch'):
- units = 'Inches'
- plot_paper_units = 0
- unit_factor = 25.4 # inch to mm
- elif units == 'mm':
- units = 'MM'
- plot_paper_units = 1
- unit_factor = 1.0
- else:
- raise DXFValueError('Supported units: "mm" and "inch"')
- # all viewport parameters are scaled paper space units
- def paper_units(value):
- return value * scale_factor
- # TODO: don't know how paper setup in DXF R12 works
- paper_width, paper_height = size
- # TODO: don't know how margins setup in DXF R12 works
- margin_top, margin_right, margin_bottom, margin_left = margins
- paper_width = paper_units(size[0])
- paper_height = paper_units(size[1])
- plimmin = self.drawing.header['$PLIMMIN'] = (0, 0)
- plimmax = self.drawing.header['$PLIMMAX'] = (paper_width, paper_height)
- # TODO: don't know how paper setup in DXF R12 works
- pextmin = self.drawing.header['$PEXTMIN'] = (0, 0, 0)
- pextmax = self.drawing.header['$PEXTMAX'] = (paper_width, paper_height, 0)
- # printing area
- printable_width = paper_width - paper_units(margin_left) - paper_units(margin_right)
- printable_height = paper_height - paper_units(margin_bottom) - paper_units(margin_top)
- # AutoCAD viewport (window) size
- vp_width = paper_width * 1.1
- vp_height = paper_height * 1.1
- # center of printing area
- center = (printable_width / 2, printable_height / 2)
- # create 'main' viewport
- main_viewport = self.add_viewport(
- center=center, # no influence to 'main' viewport?
- size=(vp_width, vp_height), # I don't get it, just use paper size!
- view_center_point=center, # same as center
- view_height=vp_height, # view height in paper space units
- )
- main_viewport.dxf.id = 1 # set as main viewport
- main_viewport.dxf.status = 2 # AutoCAD default value
- with main_viewport.edit_data() as vpdata:
- vpdata.view_mode = 1000 # AutoDesk default
- def get_paper_limits(self) -> Tuple[float, float]:
- """
- Returns paper limits in plot paper units
- """
- limmin = self.drawing.header.get('$PLIMMIN', (0, 0))
- limmax = self.drawing.header.get('$PLIMMAX', (0, 0))
- return limmin, limmax
- @property
- def layout_key(self) -> int:
- return self._paperspace
- def viewports(self) -> List['DXFEntity']:
- """
- Get all VIEWPORT entities defined in the layout. Returns a list of Viewport() objects, sorted by id, the first
- entity is always the paper space view with the id=1.
- """
- vports = [entity for entity in self if entity.dxftype() == 'VIEWPORT']
- vports.sort(key=lambda e: e.dxf.id)
- return vports
- def renumber_viewports(self) -> None:
- for num, viewport in enumerate(self.viewports(), start=1):
- viewport.dxf.id = num
- def write(self, tagwriter: 'TagWriter') -> None:
- self._entity_space.write(tagwriter)
- def add_viewport(self,
- center: Tuple[float, float],
- size: Tuple[float, float],
- view_center_point: Tuple[float, float],
- view_height: float,
- dxfattribs: dict = None) -> 'DXFEntity':
- if dxfattribs is None:
- dxfattribs = {}
- else:
- dxfattribs = dict(dxfattribs)
- width, height = size
- attribs = {
- 'center': center,
- 'width': width,
- 'height': height,
- 'status': 1, # by default highest priority (stack order)
- 'layer': 'VIEWPORTS', # use separated layer to turn off for plotting
- }
- attribs.update(dxfattribs)
- # DXF R12 (AC1009): view_center_point and view_height (as many other viewport attributes) are not usual
- # DXF attributes, they are stored as extended DXF tags.
- viewport = self.build_and_add_entity('VIEWPORT', attribs)
- viewport.dxf.id = viewport.get_next_viewport_id()
- with viewport.edit_data() as vp_data:
- vp_data.view_center_point = view_center_point
- vp_data.view_height = view_height
- return viewport
- class DXF12BlockLayout(BaseLayout):
- """
- BlockLayout has the same factory-function as Layout, but is managed
- in the BlocksSection() class. It represents a DXF Block definition.
- Attributes:
- _block_handle: db handle to BLOCK entity
- _endblk_handle: db handle to ENDBLK entity
- _entityspace: is the block content
- """
- def __init__(self, entitydb: 'EntityDB', dxffactory: 'DXFFactoryType', block_handle: str, endblk_handle: str):
- super(DXF12BlockLayout, self).__init__(dxffactory, EntitySpace(entitydb))
- self._block_handle = block_handle
- self._endblk_handle = endblk_handle
- # start of public interface
- def __contains__(self, entity: 'DXFEntity') -> bool:
- """
- Returns True if block contains entity else False. *entity* can be a handle-string, Tags(),
- ExtendedTags() or a wrapped entity.
- """
- if hasattr(entity, 'get_handle'):
- handle = entity.get_handle()
- elif hasattr(entity, 'dxf'): # it's a wrapped entity
- handle = entity.dxf.handle
- else:
- handle = entity
- return handle in self._entity_space
- @property
- def block(self) -> 'DXFEntity':
- """ Get associated BLOCK entity. """
- return self.get_entity_by_handle(self._block_handle)
- @property
- def endblk(self) -> 'DXFEntity':
- """ Get associated ENDBLK entity. """
- return self.get_entity_by_handle(self._endblk_handle)
- @property
- def name(self) -> str:
- """ Get block name """
- return self.block.dxf.name
- @name.setter
- def name(self, new_name) -> None:
- """ Set block name """
- block = self.block
- block.dxf.name = new_name
- block.dxf.name2 = new_name
- @property
- def is_layout_block(self) -> bool:
- """
- True if block is a model space or paper space block definition.
- """
- return self.block.is_layout_block
- def add_attdef(self, tag: str, insert: Sequence[float] = (0, 0), text: str = '',
- dxfattribs: dict = None) -> 'DXFEntity':
- """
- Add an :class:`Attdef` entity.
- Set position and alignment by the idiom::
- myblock.add_attdef('NAME').set_pos((2, 3), align='MIDDLE_CENTER')
- Args:
- tag: attribute name (tag) as string without spaces
- insert: attribute insert point relative to block origin (0, 0, 0)
- text: preset text for attribute
- """
- if dxfattribs is None:
- dxfattribs = {}
- dxfattribs['tag'] = tag
- dxfattribs['insert'] = insert
- dxfattribs['text'] = text
- return self.build_and_add_entity('ATTDEF', dxfattribs)
- def attdefs(self) -> Iterable['DXFEntity']:
- """
- Iterate for all :class:`Attdef` entities.
- """
- return (entity for entity in self if entity.dxftype() == 'ATTDEF')
- def has_attdef(self, tag: str) -> bool:
- """
- Returns `True` if an :class:`Attdef` for `tag` exists else `False`.
- Args:
- tag: tag name
- """
- return self.get_attdef(tag) is not None
- def get_attdef(self, tag: str) -> Optional['DXFEntity']:
- """
- Get attached :class:`Attdef` entity by `tag`.
- Args:
- tag: tag name
- Returns: :class:`Attdef`
- """
- for attdef in self.attdefs():
- if tag == attdef.dxf.tag:
- return attdef
- def get_attdef_text(self, tag: str, default: str = None) -> str:
- """
- Get content text for :class:`Attdef` `tag` as string or returns `default` if no :class:`Attdef` for `tag` exists.
- Args:
- tag: tag name
- default: default value if tag is absent
- """
- attdef = self.get_attdef(tag)
- if attdef is None:
- return default
- return attdef.dxf.text
- # end of public interface
- def add_entity(self, entity: 'DXFEntity') -> None:
- """
- Add an existing DXF entity to a layout, but be sure to unlink (:meth:`~Layout.unlink_entity()`) first the entity
- from the previous owner layout.
- Args:
- entity: :class:`DXFEntity`
- """
- self.add_handle(entity.dxf.handle)
- entity.dxf.paperspace = 0 # set a model space, because paper space layout is a different class
- for linked_entity in entity.linked_entities():
- linked_entity.dxf.paperspace = 0
- def add_handle(self, handle: str) -> None:
- """
- Add entity by handle to the block entity space.
- """
- self._entity_space.append(handle)
- def write(self, tagwriter: 'TagWriter') -> None:
- def write_tags(handle):
- tags = self._entity_space.get_tags_by_handle(handle)
- tagwriter.write_tags(tags)
- write_tags(self._block_handle)
- self._entity_space.write(tagwriter)
- write_tags(self._endblk_handle)
- def delete_all_entities(self) -> None:
- # 1. delete from database
- for handle in self._entity_space:
- del self.entitydb[handle]
- # 2. delete from entity space
- self._entity_space.delete_all_entities()
- def destroy(self) -> None:
- self.delete_all_entities()
- del self.entitydb[self._block_handle]
- del self.entitydb[self._endblk_handle]
- def get_const_attdefs(self) -> Iterable['DXFEntity']:
- """
- Returns a generator for constant ATTDEF entities.
- """
- return (attdef for attdef in self.attdefs() if attdef.is_const)
|