# Created: 21.03.2011 # Copyright (c) 2011-2018, Manfred Moitzi # License: MIT License from typing import TYPE_CHECKING, cast, Dict, Iterable, List, Union, Tuple, Any, Optional from ezdxf.entityspace import EntitySpace from ezdxf.legacy.layouts import DXF12Layout, DXF12BlockLayout from ezdxf.lldxf.extendedtags import ExtendedTags from ezdxf.lldxf.const import DXFKeyError, DXFValueError, DXFTypeError, STD_SCALES, DXFInternalEzdxfError from ezdxf.lldxf.validator import is_valid_name if TYPE_CHECKING: from ezdxf.eztypes import Drawing, TagWriter, DXFFactoryType, DXFDictionary, BlockRecord from ezdxf.eztypes import DXFEntity, Vertex, Viewport, GeoData, SortEntitiesTable PAPER_SPACE = '*Paper_Space' TMP_PAPER_SPACE_NAME = '*Paper_Space999999' class Layouts: def __init__(self, drawing: 'Drawing'): self.drawing = drawing self._layouts = {} # type: Dict[str, Layout] self._dxf_layout_management_table = None # type: DXFDictionary # key: layout name; value: layout_handle self._link_entities_section_into_blocks(drawing) self._setup() @staticmethod def _link_entities_section_into_blocks(drawing: 'Drawing') -> None: """ Link entity spaces from entities section into associated block layouts. """ blocks = drawing.blocks model_space_block = blocks.get('*MODEL_SPACE') model_space_block.set_entity_space(drawing.entities.model_space_entities()) active_layout_block = blocks.get('*PAPER_SPACE') active_layout_block.set_entity_space(drawing.entities.active_layout_entities()) drawing.entities.clear() # remove entities for entities section -> stored in blocks @property def dxffactory(self) -> 'DXFFactoryType': return self.drawing.dxffactory def _setup(self) -> None: """ Setup layout management table. """ layout_table_handle = self.drawing.rootdict['ACAD_LAYOUT'] self._dxf_layout_management_table = cast('DXFDictionary', self.dxffactory.wrap_handle(layout_table_handle)) # name ... layout name # handle ... handle to DXF object Layout for name, handle in self._dxf_layout_management_table.items(): layout = Layout(self.drawing, handle) self._layouts[name] = layout def __len__(self) -> int: """ Returns layout count. """ return len(self._layouts) def __contains__(self, name: str) -> bool: """ Returns if layout `name` exists. Args: name str: layout name """ return name in self._layouts def __iter__(self) -> Iterable['Layout']: return iter(self._layouts.values()) def modelspace(self) -> 'Layout': """ Get model space layout. Returns: Layout: model space layout """ return self.get('Model') def names(self) -> Iterable[str]: """ Returns all layout names. Returns: Iterable[str]: layout names """ return self._layouts.keys() def get(self, name: str) -> 'Layout': """ Get layout by name. Args: name (str): layout name as shown in tab, e.g. ``Model`` for model space Returns: Layout: layout """ if name is None: first_layout_name = self.names_in_taborder()[1] return self._layouts[first_layout_name] else: return self._layouts[name] def rename(self, old_name: str, new_name: str) -> None: """ Rename a layout. Layout ``Model`` can not renamed and the new name of a layout must not exist. Args: old_name (str): actual layout name new_name (str): new layout name """ if old_name == 'Model': raise ValueError('Can not rename model space.') if new_name in self._layouts: raise ValueError('Layout "{}" already exists.'.format(new_name)) layout = self._layouts[old_name] del self._layouts[old_name] layout.dxf_layout.dxf.name = new_name self._layouts[new_name] = layout def names_in_taborder(self) -> List[str]: """ Returns all layout names in tab order as a list of strings. """ names = [(layout.dxf.taborder, name) for name, layout in self._layouts.items()] return [name for order, name in sorted(names)] def get_layout_for_entity(self, entity: 'DXFEntity') -> 'Layout': """ Returns layout the `entity` resides in. Args: entity (DXFEntity): generic DXF entity """ return self.get_layout_by_key(entity.dxf.owner) def get_layout_by_key(self, layout_key: str) -> 'Layout': """ Returns a layout by its layout key. Args: layout_key (str): layout key """ for layout in self._layouts.values(): if layout_key == layout.layout_key: return layout raise DXFKeyError('Layout with key "{}" does not exist.'.format(layout_key)) def new(self, name: str, dxfattribs: dict = None) -> 'Layout': """ Create a new Layout. Args: name (str): layout name as shown in tab dxfattribs (dict): DXF attributes for the ``LAYOUT`` entity """ if not is_valid_name(name): raise DXFValueError('name contains invalid characters') if dxfattribs is None: dxfattribs = {} if name in self._layouts: raise DXFValueError('Layout "{}" already exists'.format(name)) def create_dxf_layout_entity() -> str: dxfattribs['name'] = name dxfattribs['owner'] = self._dxf_layout_management_table.dxf.handle dxfattribs.setdefault('taborder', len(self._layouts) + 1) dxfattribs['block_record'] = block_record_handle entity = self.drawing.objects.create_new_dxf_entity('LAYOUT', dxfattribs) return entity.dxf.handle block_layout = self.drawing.blocks.new_layout_block() block_record_handle = block_layout.block_record_handle block_record = block_layout.block_record layout_handle = create_dxf_layout_entity() block_record.dxf.layout = layout_handle # create valid layout entity layout = Layout(self.drawing, layout_handle) # add layout to management tables self._dxf_layout_management_table[name] = layout_handle self._layouts[name] = layout return layout def set_active_layout(self, name: str) -> None: """ Set active paper space layout. Args: name (str): layout name as shown in tab """ if name == 'Model': # reserved layout name raise DXFValueError('Can not set model space as active layout') new_active_layout = self.get(name) # raises KeyError if no layout 'name' exists old_active_layout_key = self.drawing.get_active_layout_key() if old_active_layout_key == new_active_layout.layout_key: return # layout 'name' is already the active layout blocks = self.drawing.blocks new_active_paper_space_name = new_active_layout.block_record_name blocks.rename_block(PAPER_SPACE, TMP_PAPER_SPACE_NAME) blocks.rename_block(new_active_paper_space_name, PAPER_SPACE) blocks.rename_block(TMP_PAPER_SPACE_NAME, new_active_paper_space_name) def delete(self, name: str) -> None: """ Delete layout `name` and all entities in it. Args: name (str): layout name as shown in tabs Raises: KeyError: if layout `name` do not exists ValueError: if `name` is ``Model`` (deleting model space) """ if name == 'Model': raise DXFValueError("Can not delete model space layout.") layout = self._layouts[name] if layout.layout_key == self.drawing.get_active_layout_key(): # name is the active layout for layout_name in self.names(): if layout_name not in (name, 'Model'): # set any other layout as active layout self.set_active_layout(layout_name) break self._dxf_layout_management_table.remove(layout.name) del self._layouts[layout.name] layout.destroy() def active_layout(self) -> 'Layout': """ Returns active paper space layout. """ for layout in self: if layout.block_record_name.upper() == '*PAPER_SPACE': return layout raise DXFInternalEzdxfError('No active paper space found.') def write_entities_section(self, tagwriter: 'TagWriter') -> None: """ Write ``ENTITIES`` section to DXF file, the ``ENTITIES`` section consist of all entities in model space and active paper space layout. All DXF entities of the remaining paper space layouts are stored in their associated ``BLOCK`` entity in the ``BLOCKS`` section. Args: tagwriter (TagWriter): tag writer object """ self.modelspace().write(tagwriter) self.active_layout().write(tagwriter) class Layout(DXF12Layout): """ Layout representation Every layout consist of a LAYOUT entity in the OBJECTS section, an associated BLOCK in the BLOCKS section and a BLOCK_RECORD_TABLE entry. layout_key: handle of the BLOCK_RECORD, every layout entity has this handle as owner attribute (entity.dxf.owner) There are 3 different layout types: 1. Model Space - not deletable, all entities of this layout are stored in the DXF file in the ENTITIES section, the associated ``*Model_Space`` block is empty, block name ``*Model_Space`` is mandatory, the layout name is ``Model`` and it is mandatory. 2. Active Layout - all entities of this layout are stored in the ENTITIES section, the associated ``*Paper_Space`` block is empty, block name ``*Paper_Space`` is mandatory and also marks the active layout, the layout name can be an arbitrary string. 3. Inactive Layout - all entities of this layouts are stored in the associated BLOCK called ``*Paper_SpaceN``, where ``N`` is an arbitrary number, I don't know if the block name schema '*Paper_SpaceN' is mandatory, the layout name can be an arbitrary string. There is no different handling for active layouts and inactive layouts in ezdxf, this differentiation is just for AutoCAD important and it is not documented in the DXF reference. Internal Structure For **every** layout exists a BlockLayout() object in the BLOCKS section and a Layout() object in Layouts(). The entity space of the BlockLayout() object and the entity space of the Layout() object are the **same** object. """ # plot_layout_flags of LAYOUT entity PLOT_VIEWPORT_BORDERS = 1 SHOW_PLOT_STYLES = 2 PLOT_CENTERED = 4 PLOT_HIDDEN = 8 USE_STANDARD_SCALE = 16 PLOT_PLOTSTYLES = 32 SCALE_LINEWEIGHTS = 64 PRINT_LINEWEIGHTS = 128 DRAW_VIEWPORTS_FIRST = 512 MODEL_TYPE = 1024 UPDATE_PAPER = 2048 ZOOM_TO_PAPER_ON_UPDATE = 4096 INITIALIZING = 8192 PREV_PLOT_INIT = 16384 def __init__(self, drawing, layout_handle): dxffactory = drawing.dxffactory self.dxf_layout = dxffactory.wrap_handle(layout_handle) self._block_record_handle = self.dxf_layout.dxf.block_record entity_space = self._get_layout_entity_space(drawing, self.dxf_layout) super(Layout, self).__init__(entity_space, dxffactory, 0) self._layout_handle = layout_handle self._paperspace = 0 if self.name == 'Model' else 1 self._repair_owner_tags() @staticmethod def _get_layout_entity_space(drawing: 'Drawing', layout: 'Layout') -> 'EntitySpace': block_record = drawing.dxffactory.wrap_handle(layout.dxf.block_record) block = drawing.blocks.get(block_record.dxf.name) return block.get_entity_space() def _repair_owner_tags(self) -> None: """ Set `owner` and `paperspace` attributes of entities hosted by this layout to correct values. """ layout_key = self.layout_key paper_space = self._paperspace for entity in self: if entity.get_dxf_attrib('owner', default=None) != layout_key: entity.set_dxf_attrib('owner', layout_key) if entity.get_dxf_attrib('paperspace', default=0) != paper_space: entity.set_dxf_attrib('paperspace', paper_space) # start of public interface def __contains__(self, entity: Union['DXFEntity', str]) -> bool: if isinstance(entity, str): # entity is a handle string entity = self.get_entity_by_handle(entity) return entity.dxf.owner == self.layout_key @property def name(self) -> str: """ Returns layout name (as shown in tabs). """ return self.dxf_layout.dxf.name @property def dxf(self) -> Any: # dynamic DXF attribute dispatching, e.g. DXFLayout.dxf.layout_flags """ Returns the DXF name space attribute of the associated DXF ``LAYOUT`` entity. This enables direct access to the ``LAYOUT`` entity, e.g. Layout.dxf.layout_flags """ return self.dxf_layout.dxf def page_setup(self, size: Tuple[int, int] = (297, 210), margins: Tuple[float, float, float, float] = (10, 15, 10, 15), units: str = 'mm', offset: Tuple[float, float] = (0, 0), rotation: int = 0, scale: int = 16, name: str = 'ezdxf', device: str = 'DWG to PDF.pc3') -> None: """ Setup plot settings and paper size and reset viewports. All parameters in given `units` (mm or inch). Args: size: paper size as (width, height) tuple margins: (top, right, bottom, left) hint: clockwise units: "mm" or "inch" offset: plot origin offset is 2D point rotation: see table `Rotation` scale: int 0-32 = standard scale type or tuple(numerator, denominator) e.g. (1, 50) for 1:50 name: paper name prefix '{name}_({width}_x_{height}_{unit})' device: device .pc3 configuration file or system printer name === ============ int Rotation === ============ 0 no rotation 1 90 degrees counter-clockwise 2 upside-down 3 90 degrees clockwise === ============ """ if self.name == 'Model': raise DXFTypeError("No paper setup for model space.") if int(rotation) not in (0, 1, 2, 3): raise DXFValueError("valid rotation values: 0-3") if isinstance(scale, tuple): standard_scale = 16 elif isinstance(scale, int): standard_scale = scale scale = STD_SCALES.get(standard_scale, (1, 1)) else: raise DXFTypeError("scale has to be an int or a tuple(numerator, denominator)") if scale[0] == 0: raise DXFValueError("scale numerator can't be 0.") if scale[1] == 0: raise DXFValueError("scale denominator can't be 0.") self.use_standard_scale(False) # works best, don't know why paper_width, paper_height = size margin_top, margin_right, margin_bottom, margin_left = margins 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"') # Setup PLOTSETTINGS # all paper sizes in mm dxf = self.dxf_layout.dxf dxf.page_setup_name = '' dxf.plot_configuration_file = device dxf.paper_size = '{0}_({1:.2f}_x_{2:.2f}_{3})'.format(name, paper_width, paper_height, units) dxf.left_margin = margin_left * unit_factor dxf.bottom_margin = margin_bottom * unit_factor dxf.right_margin = margin_right * unit_factor dxf.top_margin = margin_top * unit_factor dxf.paper_width = paper_width * unit_factor dxf.paper_height = paper_height * unit_factor dxf.scale_numerator = scale[0] dxf.scale_denominator = scale[1] dxf.plot_paper_units = plot_paper_units dxf.plot_rotation = rotation x_offset, y_offset = offset dxf.plot_origin_x_offset = x_offset * unit_factor # conversion to mm dxf.plot_origin_y_offset = y_offset * unit_factor # conversion to mm dxf.standard_scale_type = standard_scale dxf.unit_factor = 1. / unit_factor # 1/1 for mm; 1/25.4 ... for inch # Setup Layout self.reset_paper_limits() self.reset_extends() self.reset_viewports() def reset_extends(self) -> None: """ Reset paper space extends. (in :meth:`~Layout.page_setup` included) """ dxf = self.dxf_layout.dxf dxf.extmin = (+1e20, +1e20, +1e20) # AutoCAD default dxf.extmax = (-1e20, -1e20, -1e20) # AutoCAD default def reset_paper_limits(self) -> None: """ Set paper limits to default values, all values in paper space units but without plot scale (?). (in :meth:`~Layout.page_setup` included) """ dxf = self.dxf_layout.dxf if dxf.plot_paper_units == 0: # inch unit_factor = 25.4 else: # mm unit_factor = 1.0 # all paper sizes are stored in mm paper_width = dxf.paper_width / unit_factor # in plot paper units paper_height = dxf.paper_height / unit_factor # in plot paper units left_margin = dxf.left_margin / unit_factor bottom_margin = dxf.bottom_margin / unit_factor x_offset = dxf.plot_origin_x_offset / unit_factor y_offset = dxf.plot_origin_y_offset / unit_factor # plot origin is the lower left corner of the printable paper area # limits are the paper borders relative to the plot origin shift_x = left_margin + x_offset shift_y = bottom_margin + y_offset dxf.limmin = (-shift_x, -shift_y) # paper space units dxf.limmax = (paper_width - shift_x, paper_height - shift_y) def get_paper_limits(self) -> Tuple[Tuple[float, float], Tuple[float, float]]: """ Returns paper limits in plot paper units, relative to the plot origin, as tuple ((x1, y1), (x2, y2)). Lower left corner is (x1, y1), upper right corner is (x2, y2). plot origin = lower left corner of printable area + plot origin offset """ return self.dxf.limmin, self.dxf.limmax def reset_viewports(self) -> None: """ Delete all existing viewports, and add a new main viewport. (in :meth:`~Layout.page_setup` included) """ # remove existing viewports for viewport in self.viewports(): self.delete_entity(viewport) self.add_new_main_viewport() def add_new_main_viewport(self) -> None: """ Add a new main viewport. """ dxf = self.dxf_layout.dxf if dxf.plot_paper_units == 0: # inches unit_factor = 25.4 else: # mm unit_factor = 1.0 # all paper parameters in mm! # all viewport parameters in paper space units inch/mm + scale factor! scale_factor = dxf.scale_denominator / dxf.scale_numerator def paper_units(value): return value / unit_factor * scale_factor paper_width = paper_units(dxf.paper_width) paper_height = paper_units(dxf.paper_height) # plot origin offset x_offset = paper_units(dxf.plot_origin_x_offset) y_offset = paper_units(dxf.plot_origin_y_offset) # printing area printable_width = paper_width - paper_units(dxf.left_margin) - paper_units(dxf.right_margin) printable_height = paper_height - paper_units(dxf.bottom_margin) - paper_units(dxf.top_margin) # AutoCAD viewport (window) size vp_width = paper_width * 1.1 vp_height = paper_height * 1.1 # center of printing area center = (printable_width / 2 - x_offset, printable_height / 2 - y_offset) # 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.flags = 557088 # AutoCAD default value dxf.viewport = main_viewport.dxf.handle def set_plot_type(self, value: int = 5) -> None: """ === ============================================================ 0 last screen display 1 drawing extents 2 drawing limits 3 view specific (defined by Layout.dxf.plot_view_name) 4 window specific (defined by Layout.set_plot_window_limits()) 5 layout information (default) === ============================================================ Args: value: plot type Raises: DXFValueError: for `value` out of range """ if 0 <= int(value) <= 5: self.dxf.plot_type = value # type: ignore else: raise DXFValueError('Plot type value out of range (0-5).') def set_plot_style(self, name: str = 'ezdxf.ctb', show: bool = False) -> None: """ Set plot style file of type `ctb`. Args: name: plot style filename show: show plot style effect in preview? (AutoCAD specific attribute) """ self.dxf_layout.dxf.current_style_sheet = name self.use_plot_styles(True) self.show_plot_styles(show) def set_plot_window(self, lower_left: Tuple[float, float] = (0, 0), upper_right: Tuple[float, float] = (0, 0)) -> None: """ Set plot window size in (scaled) paper space units. Args: lower_left: lower left corner as 2D point upper_right: upper right corner as 2D point """ x1, y1 = lower_left x2, y2 = upper_right dxf = self.dxf_layout.dxf dxf.plot_window_x1 = x1 dxf.plot_window_y1 = y1 dxf.plot_window_x2 = x2 dxf.plot_window_y2 = y2 self.set_plot_type(4) # plot layout flags setter def plot_viewport_borders(self, state: bool = True) -> None: self.set_plot_flags(self.PLOT_VIEWPORT_BORDERS, state) def show_plot_styles(self, state: bool = True) -> None: self.set_plot_flags(self.SHOW_PLOT_STYLES, state) def plot_centered(self, state: bool = True) -> None: self.set_plot_flags(self.PLOT_CENTERED, state) def plot_hidden(self, state: bool = True) -> None: self.set_plot_flags(self.PLOT_HIDDEN, state) def use_standard_scale(self, state: bool = True) -> None: self.set_plot_flags(self.USE_STANDARD_SCALE, state) def use_plot_styles(self, state: bool = True) -> None: self.set_plot_flags(self.PLOT_PLOTSTYLES, state) def scale_lineweights(self, state: bool = True) -> None: self.set_plot_flags(self.SCALE_LINEWEIGHTS, state) def print_lineweights(self, state: bool = True) -> None: self.set_plot_flags(self.PRINT_LINEWEIGHTS, state) def draw_viewports_first(self, state: bool = True) -> None: self.set_plot_flags(self.PRINT_LINEWEIGHTS, state) def model_type(self, state: bool = True) -> None: self.set_plot_flags(self.MODEL_TYPE, state) def update_paper(self, state: bool = True) -> None: self.set_plot_flags(self.UPDATE_PAPER, state) def zoom_to_paper_on_update(self, state: bool = True) -> None: self.set_plot_flags(self.ZOOM_TO_PAPER_ON_UPDATE, state) def plot_flags_initializing(self, state: bool = True) -> None: self.set_plot_flags(self.INITIALIZING, state) def prev_plot_init(self, state: bool = True) -> None: self.set_plot_flags(self.PREV_PLOT_INIT, state) def set_plot_flags(self, flag, state: bool = True) -> None: self.dxf_layout.set_flag_state(flag, state=state, name='plot_layout_flags') def add_viewport(self, center: 'Vertex', size: Tuple[float, float], view_center_point: 'Vertex', view_height: float, dxfattribs: dict = None) -> 'Viewport': dxfattribs = dxfattribs or {} 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 'view_center_point': view_center_point, 'view_height': view_height, } attribs.update(dxfattribs) viewport = cast('Viewport', self.build_and_add_entity('VIEWPORT', attribs)) viewport.dxf.id = viewport.get_next_viewport_id() return viewport # end of public interface @property def layout_key(self) -> str: """ Returns the layout key as string. The layout key is the handle of the associated ``BLOCK_RECORD`` entry in the ``BLOCK_RECORDS`` table. """ return self._block_record_handle @property def block_record(self) -> 'BlockRecord': """ Returns the associated ``BLOCK_RECORD``. """ return self.drawing.dxffactory.wrap_handle(self._block_record_handle) @property def block_record_name(self) -> str: """ Returns the name of the associated ``BLOCK_RECORD`` as string. """ return self.block_record.dxf.name @property def block(self) -> 'BlockLayout': """ Returns the associated `BlockLayout` object. """ return self.drawing.blocks.get(self.block_record_name) def _set_paperspace(self, entity: 'DXFEntity') -> None: """ Set correct `owner` and `paperspace` attribute, to be a valid member of this layout. Args: entity (DXFEntiy): generic DXF entity """ entity.dxf.paperspace = self._paperspace entity.dxf.owner = self.layout_key def destroy(self) -> None: """ Delete all member entities and the layout itself from entity database and all other structures. """ self.delete_all_entities() self.drawing.blocks.delete_block(self.block.name) self.drawing.objects.remove_handle(self._layout_handle) self.drawing.entitydb.delete_handle(self._layout_handle) def get_extension_dict(self, create: bool = True) -> 'DXFDictionary': """ Returns the associated extension dictionary. Args: create (bool): create extension dictionary if not exists Raises: DXFValueError: if extension dictionary do not exists and `create` is False """ block_record = self.block_record try: xdict = block_record.get_extension_dict() except (DXFValueError, DXFKeyError): # DXFValueError: block_record has no extension dict # DXFKeyError: block_record has an extension dict handle, but extension dict does not exist if create: xdict = block_record.new_extension_dict() else: raise DXFValueError('Extension dictionary do not exist.') return xdict def new_geodata(self, dxfattribs: dict = None) -> 'GeoData': """ Creates a new :class:`GeoData` entity and replaces existing ones. The GEODATA entity resides in the OBJECTS section and NOT in the layout entity space and it is linked to the layout by an extension dictionary located in BLOCK_RECORD of the layout. The GEODATA entity requires DXF version R2010+. The DXF Reference does not document if other layouts than model space supports geo referencing, so getting/setting geo data may only make sense for the model space layout, but it is also available in paper space layouts. Args: dxfattribs (dict): DXF attributes for the :class:`GeoData` entity """ if dxfattribs is None: dxfattribs = {} dwg = self.drawing if dwg.dxfversion < 'AC1024': raise DXFValueError('GEODATA entity requires DXF version R2010 (AC1024) or later.') xdict = self.get_extension_dict(create=True) geodata = dwg.objects.add_geodata( owner=xdict.dxf.handle, dxfattribs=dxfattribs, ) xdict['ACAD_GEOGRAPHICDATA'] = geodata.dxf.handle return geodata def get_geodata(self) -> Optional['GeoData']: """ Returns the :class:`GeoData` entity associated to this layout or None. """ try: xdict = self.block_record.get_extension_dict() except DXFValueError: return None try: return xdict.get_entity('ACAD_GEOGRAPHICDATA') except DXFKeyError: return None def get_sortents_table(self, create: bool = True) -> 'SortEntitiesTable': """ Get/Create to layout associated ``SORTENTSTABLE`` object. Args: create (bool): new table if table do not exists and create is True Raises: DXFValueError: if table not exists and `create` is False """ xdict = self.get_extension_dict(create=True) try: sortents_table = xdict.get_entity('ACAD_SORTENTS') except DXFKeyError: if create: sortents_table = self.drawing.objects.create_new_dxf_entity( 'SORTENTSTABLE', dxfattribs={ 'owner': xdict.dxf.handle, 'block_record': self.layout_key }, ) xdict['ACAD_SORTENTS'] = sortents_table.dxf.handle else: raise DXFValueError('Extension dictionary entry ACAD_SORTENTS do not exist.') return sortents_table def set_redraw_order(self, handles: Union[Dict, Iterable[Tuple[str, str]]]) -> None: """ If the header variable $SORTENTS `Regen` flag (bit-code value 16) is set, AutoCAD regenerates entities in ascending handles order. To change redraw order associate a different sort handle to entities, this redefines the order in which the entities are regenerated. `handles` can be a dict of object_handle and sort_handle as (key, value) pairs, or an iterable of (object_handle, sort_handle) tuples. The sort_handle doesn't have to be unique, same or all handles can share the same sort_handle and sort_handles can use existing handles too. The '0' handle can be used, but this sort_handle will be drawn as latest (on top of all other entities) and not as first as expected. Args: handles: iterable or dict of handle associations; for iterable an association is a tuple (object_handle, sort_handle); for dict the association is key: object_handle, value: sort_handle """ sortents = self.get_sortents_table() if isinstance(handles, dict): handles = handles.items() sortents.set_handles(handles) def get_redraw_order(self) -> Iterable[Tuple[str, str]]: """ Returns iterable for all existing table entries as (object_handle, sort_handle) pairs. (see also :meth:`~Layout.set_redraw_order`) """ empty = [] try: xdict = self.get_extension_dict(create=False) except DXFValueError: return empty try: sortents_table = xdict.get_entity('ACAD_SORTENTS') except DXFKeyError: return empty return iter(sortents_table) class BlockLayout(DXF12BlockLayout): 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` """ # entity can be ExtendedTags() or a GraphicEntity() or inherited wrapper class if isinstance(entity, ExtendedTags): entity = self._dxffactory.wrap_entity(entity) entity.dxf.owner = self.block_record_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.owner = self.block_record_handle linked_entity.dxf.paperspace = 0 self._entity_space.append(entity.dxf.handle) @property def block_record_handle(self) -> str: return self.block.dxf.owner def set_block_record_handle(self, block_record_handle: str) -> None: self.block.dxf.owner = block_record_handle self.endblk.dxf.owner = block_record_handle @property def block_record(self) -> 'BlockRecord': return self.drawing.dxffactory.wrap_handle(self.block_record_handle) def get_entity_space(self) -> EntitySpace: return self._entity_space def set_entity_space(self, entity_space: EntitySpace) -> None: self._entity_space = entity_space def destroy(self) -> None: self.drawing.sections.tables.block_records.remove_handle(self.block_record_handle) super(BlockLayout, self).destroy() def write(self, tagwriter: 'TagWriter'): # BLOCK section: do not write content of model space and active layout if self.name.upper() in ('*MODEL_SPACE', '*PAPER_SPACE'): save = self._entity_space self._entity_space = EntitySpace(self.entitydb) super(BlockLayout, self).write(tagwriter) self._entity_space = save else: super(BlockLayout, self).write(tagwriter)