Пример #1
0
    def move_to_layout(self,
                       layout: 'BaseLayout',
                       source: 'BaseLayout' = 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. Moving between different DXF drawings is not supported.

        Args:
            layout: any layout (model space, paper space, block)
            source: provide source layout, faster for DXF R12, if entity is in a block layout

        Raises:
            DXFStructureError: for moving between different DXF drawings

        """
        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)
Пример #2
0
    def __init__(self,
                 control_points: Iterable['Vertex'],
                 order: int = 4,
                 knots: Iterable[float] = None,
                 weights: Iterable[float] = None):
        self.control_points = Vector.list(control_points)
        self.order = order
        if order > self.count:
            raise DXFValueError(
                'Invalid need more control points for order {}'.format(order))

        if knots is None:
            knots = open_uniform_knot_vector(self.count, self.order)
        else:
            knots = list(knots)
            if len(knots) != self.nplusc:
                raise ValueError("{} knot values required, got {}.".format(
                    self.nplusc, len(knots)))

        self.basis = Basis(knots, self.order, self.count, weights=weights)
Пример #3
0
    def move_to_layout(self, entity: 'DXFGraphic',
                       layout: 'BaseLayout') -> None:
        """
        Move entity to another layout.

        Args:
            entity: DXF entity to move
            layout: any layout (modelspace, paperspace, block) from **same** drawing

        """
        if entity.doc != layout.doc:
            raise DXFStructureError(
                'Moving between different DXF drawings is not supported.')

        try:
            self.unlink_entity(entity)
        except ValueError:
            raise DXFValueError('Layout does not contain entity.')
        else:
            layout.add_entity(entity)
Пример #4
0
    def get_xlist(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:
            DXFKeyError: XDATA `appid` does not exist
            DXFValueError: list `name` does not exist

        """
        xdata = self.get(appid)
        try:
            return get_named_list_from_xdata(name, xdata)
        except NotFoundException:
            raise DXFValueError(
                f'No data list "{name}" not found for APPID "{appid}"')
Пример #5
0
    def set_blk_handle(self, attr: str, arrow_name: str) -> None:
        if arrow_name == ARROWS.closed_filled:
            # special arrow, no handle needed (is '0' if set)
            # do not create block by default, this will be done if arrow is used
            # and block record handle is not needed here
            self.dxf.discard(attr)
            return

        blocks = self.doc.blocks
        if ARROWS.is_acad_arrow(arrow_name):
            # create block, because need block record handle is needed here
            block_name = ARROWS.create_block(blocks, arrow_name)
        else:
            block_name = arrow_name

        blk = blocks.get(block_name)
        if blk is not None:
            self.set_dxf_attrib(attr, blk.block_record_handle)
        else:
            raise DXFValueError(f'Block {arrow_name} does not exist.')
Пример #6
0
    def new(self, name: str) -> 'DXFObject':
        """  Create a new object of type `self.object_type` and store its handle
        in the object manager dictionary.

        Args:
            name: name of new object as string

        Returns:
            new object of type `self.object_type`

        Raises:
            DXFValueError: if object name already exist

        (internal API)

        """
        if name in self.object_dict:
            raise DXFValueError(
                f'{self.object_type} entry {name} already exists.')
        return self._new(name, dxfattribs={'name': name})
Пример #7
0
    def to_ellipse(self, replace=True) -> "Ellipse":
        """Convert CIRCLE/ARC to an :class:`~ezdxf.entities.Ellipse` entity.

        Adds the new ELLIPSE entity to the entity database and to the
        same layout as the source entity.

        Args:
            replace: replace (delete) source entity by ELLIPSE entity if ``True``

        """
        from ezdxf.entities import Ellipse

        layout = self.get_layout()
        if layout is None:
            raise DXFValueError("valid layout required")
        ellipse = Ellipse.from_arc(self)
        if replace:
            replace_entity(self, ellipse, layout)
        else:
            add_entity(ellipse, layout)
        return ellipse
Пример #8
0
    def to_spline(self, replace=True) -> "Spline":
        """Convert CIRCLE/ARC to a :class:`~ezdxf.entities.Spline` entity.

        Adds the new SPLINE entity to the entity database and to the
        same layout as the source entity.

        Args:
            replace: replace (delete) source entity by SPLINE entity if ``True``

        """
        from ezdxf.entities import Spline

        layout = self.get_layout()
        if layout is None:
            raise DXFValueError("valid layout required")
        spline = Spline.from_arc(self)
        if replace:
            replace_entity(self, spline, layout)
        else:
            add_entity(spline, layout)
        return spline
Пример #9
0
def bspline_control_frame(fit_points, degree=3, method='distance', power=.5):
    """
    Calculate B-spline control frame, given are the fit points and the degree of the B-spline.

        1. method = 'uniform', creates a uniform t vector, [0 .. 1] equally spaced
        2. method = 'distance', creates a t vector with values proportional to the fit point distances
        3. method = 'centripetal', creates a t vector with values proportional to the fit point distances^power

    Args:
        fit_points: fit points of B-spline
        degree: degree of B-spline
        method: calculation method for parameter vector t
        power: power for centripetal method

    """
    def create_t_vector():
        if method == 'uniform':
            return uniform_t_vector(fit_points)  # equally spaced 0 .. 1
        elif method == 'distance':
            return distance_t_vector(fit_points)
        elif method == 'centripetal':
            return centripetal_t_vector(fit_points, power=power)
        else:
            raise DXFValueError('Unknown method: {}'.format(method))

    fit_points = list(fit_points)
    count = len(fit_points)
    order = degree + 1
    if order > count:
        raise DXFValueError(
            'Need more fit points for degree {}'.format(degree))

    t_vector = list(create_t_vector())
    knots = list(control_frame_knots(count - 1, degree, t_vector))
    control_points = global_curve_interpolation(fit_points, degree, t_vector,
                                                knots)
    bspline = BSpline(control_points, order=order, knots=knots)
    bspline.t_array = t_vector
    return bspline
Пример #10
0
    def set_plot_type(self, value: int = 5) -> None:
        """
        === ============================================================
        0   last screen display
        1   drawing extents
        2   drawing limits
        3   view specific (defined by :attr:`Layout.dxf.plot_view_name`)
        4   window specific (defined by :meth:`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).')
Пример #11
0
    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
Пример #12
0
    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)
Пример #13
0
def bspline_control_frame_approx(fit_points: Iterable['Vertex'], count: int, degree: int = 3, method: str = 'distance',
                                 power: float = .5):
    """
    Approximate B-spline by a reduced count of control points, given are the fit points and the degree of the B-spline.

        1. method = 'uniform', creates a uniform t vector, [0 .. 1] equally spaced
        2. method = 'distance', creates a t vector with values proportional to the fit point distances
        3. method = 'centripetal', creates a t vector with values proportional to the fit point distances^power

    Args:
        fit_points: all fit points of B-spline
        count: count of designated control points
        degree: degree of B-spline
        method: calculation method for parameter vector t
        power: power for centripetal method

    """

    def create_t_vector():
        if method == 'uniform':
            return uniform_t_vector(fit_points)  # equally spaced 0 .. 1
        elif method == 'distance':
            return distance_t_vector(fit_points)
        elif method == 'centripetal':
            return centripetal_t_vector(fit_points, power=power)
        else:
            raise DXFValueError('Unknown method: {}'.format(method))

    fit_points = list(fit_points)
    order = degree + 1
    if order > count:
        raise DXFValueError('More control points for degree {} required.'.format(degree))

    t_vector = list(create_t_vector())
    knots = list(control_frame_knots(len(fit_points) - 1, degree, t_vector))
    control_points = global_curve_approximation(fit_points, count, degree, t_vector, knots)
    bspline = BSpline(control_points, order=order)
    return bspline
Пример #14
0
    def add_underlay_def(self, filename: str, format: str = 'pdf', name: str = None) -> 'UnderlayDef':
        """
        Add an :class:`~ezdxf.entities.underlay.UnderlayDef` entity to the drawing (OBJECTS section).
        `filename` is the underlay file name as relative or absolute path and `format` as string (pdf, dwf, dgn).
        The underlay definition is required to create an underlay reference.

        Args:
            filename: underlay file name
            format: file format as string ``'pdf'|'dwf'|'dgn'`` or ``'ext'`` for getting file format from filename extension
            name: pdf format = page number to display; dgn format = ``'default'``; dwf: ????

        """
        fmt = format.upper()
        if fmt in ('PDF', 'DWF', 'DGN'):
            underlay_dict_name = 'ACAD_{}DEFINITIONS'.format(fmt)
            underlay_def_entity = "{}DEFINITION".format(fmt)
        else:
            raise DXFValueError("Unsupported file format: '{}'".format(fmt))

        if name is None:
            if fmt == 'PDF':
                name = '1'  # Display first page by default
            elif fmt == 'DGN':
                name = 'default'
            else:
                name = 'Model'  # Display model space for DWF ???

        underlay_dict = self.rootdict.get_required_dict(underlay_dict_name)
        underlay_def = self.new_entity(underlay_def_entity, dxfattribs={
            'owner': underlay_dict.dxf.handle,
            'filename': filename,
            'name': name,
        })

        # auto-generated underlay key
        key = self.dxffactory.next_underlay_key(lambda k: k not in underlay_dict)
        underlay_dict[key] = underlay_def.dxf.handle
        return cast('UnderlayDef', underlay_def)
Пример #15
0
def bspline_control_frame_approx(fit_points: Iterable['Vertex'], count: int, degree: int = 3, method: str = 'distance',
                                 power: float = .5):
    """
    Approximate `B-spline`_ by a reduced count of control points, given are the fit points and the degree of
    the B-spline.

    Args:
        fit_points: all fit points of B-spline as :class:`Vector` compatible objects
        count: count of designated control points
        degree: degree of B-spline
        method: calculation method for parameter vector t, see :func:`bspline_control_frame`
        power: power for centripetal method

    Returns:
        :class:`BSpline`

    """

    def create_t_vector():
        if method == 'uniform':
            return uniform_t_vector(fit_points)  # equally spaced 0 .. 1
        elif method == 'distance':
            return distance_t_vector(fit_points)
        elif method == 'centripetal':
            return centripetal_t_vector(fit_points, power=power)
        else:
            raise DXFValueError('Unknown method: {}'.format(method))

    fit_points = list(fit_points)
    order = degree + 1
    if order > count:
        raise DXFValueError('More control points for degree {} required.'.format(degree))

    t_vector = list(create_t_vector())
    knots = list(control_frame_knots(len(fit_points) - 1, degree, t_vector))
    control_points = global_curve_approximation(fit_points, count, degree, t_vector, knots)
    bspline = BSpline(control_points, order=order)
    return bspline
Пример #16
0
def required_knot_values(count: int, order: int) -> int:
    """
    Returns the count of required knot values for a B-spline of `order` and `count` control points.

    degree =  degree of B-spline, in math papers often called: `p`

    Args:
        count: count of control points, in math papers often called:  `n` + 1
        order: order of B-Spline, in math papers often called:  `k`

    Relationships:

    - `k` (order) = `p` (degree) + 1
    - 2 ≤ `k` (order) ≤ `n` + 1 (count)

    """
    k = order
    n = count - 1
    p = k - 1
    if not (2 <= k <= (n + 1)):
        raise DXFValueError('Invalid count/order combination')
    # n + p + 2 = count + order
    return n + p + 2
Пример #17
0
def control_frame_knots(n: int, p: int, t_vector: Iterable[float]) -> Iterable[float]:
    """
    Generates a 'clamped' knot vector for control frame creation. All knot values in the range [0 .. 1].

    Args:
        n: count fit points - 1
        p: degree of spline
        t_vector: parameter vector, length(t_vector) == n+1

    Yields: n+p+2 knot values as floats

    """
    order = int(p + 1)
    if order > (n + 1):
        raise DXFValueError('Invalid n/p combination')

    t_vector = [float(t) for t in t_vector]
    for _ in range(order):  # clamped spline has 'order' leading 0s
        yield t_vector[0]
    for j in range(1, n - p + 1):
        yield sum(t_vector[j: j + p]) / p
    for _ in range(order):  # clamped spline has 'order' appended 1s
        yield t_vector[-1]
Пример #18
0
 def dispatch(self,
              override: "DimStyleOverride",
              ucs: "UCS" = None) -> "BaseDimensionRenderer":
     dimension = override.dimension
     dim_type = dimension.dimtype
     dxf_type = dimension.dxftype()
     if dxf_type == "ARC_DIMENSION":
         return self.arc_length(dimension, ucs, override)
     elif dxf_type == "LARGE_RADIAL_DIMENSION":
         return self.large_radial(dimension, ucs, override)
     elif dim_type in (0, 1):
         return self.linear(dimension, ucs, override)
     elif dim_type == 2:
         return self.angular(dimension, ucs, override)
     elif dim_type == 3:
         return self.diameter(dimension, ucs, override)
     elif dim_type == 4:
         return self.radius(dimension, ucs, override)
     elif dim_type == 5:
         return self.angular3p(dimension, ucs, override)
     elif dim_type == 6:
         return self.ordinate(dimension, ucs, override)
     else:
         raise DXFValueError(f"Unknown DIMENSION type: {dim_type}")
Пример #19
0
    def delete(self, name: str) -> None:
        """ Delete layout `name` and all entities owned by it.

        Args:
            name (str): layout name as shown in tabs

        Raises:
            KeyError: if layout `name` do not exists
            ValueError: if `name` is ``'Model'``, deleting modelspace is not possible

        """
        assert isinstance(name, str), type(name)
        if name == 'Model':
            raise DXFValueError("Can not delete model space layout.")

        layout = self._layouts[name]
        if layout.layout_key == self.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_layouts.remove(layout.name)
        del self._layouts[layout.name]
        layout.destroy()
Пример #20
0
 def layers(self, names):
     if isstring(names):
         raise DXFValueError('requires iterable but not string')
     self.layers[:] = list(names)
Пример #21
0
def groupby(entities: Iterable['DXFEntity'], dxfattrib: str = '', key: 'KeyFunc' = None) \
        -> Dict[Hashable, List['DXFEntity']]:
    """
    Groups a sequence of DXF entities by a DXF attribute like ``'layer'``, returns a dict with `dxfattrib` values
    as key and a list of entities matching this `dxfattrib`.
    A `key` function can be used to combine some DXF attributes (e.g. layer and color) and should return a
    hashable data type like a tuple of strings, integers or floats, `key` function example::

        def group_key(entity: DXFEntity):
            return entity.dxf.layer, entity.dxf.color

    For not suitable DXF entities return ``None`` to exclude this entity, in this case it's not required, because
    :func:`groupby` catches :class:`DXFAttributeError` exceptions to exclude entities, which do not provide
    layer and/or color attributes, automatically.

    Result dict for `dxfattrib` = ``'layer'`` may look like this::

        {
            '0': [ ... list of entities ],
            'ExampleLayer1': [ ... ],
            'ExampleLayer2': [ ... ],
            ...
        }

    Result dict for `key` = `group_key`, which returns a ``(layer, color)`` tuple, may look like this::

        {
            ('0', 1): [ ... list of entities ],
            ('0', 3): [ ... ],
            ('0', 7): [ ... ],
            ('ExampleLayer1', 1): [ ... ],
            ('ExampleLayer1', 2): [ ... ],
            ('ExampleLayer1', 5): [ ... ],
            ('ExampleLayer2', 7): [ ... ],
            ...
        }

    All entity containers (modelspace, paperspace layouts and blocks) and the :class:`~ezdxf.query.EntityQuery` object
    have a dedicated :meth:`groupby` method.

    Args:
        entities: sequence of DXF entities to group by a DXF attribute or a `key` function
        dxfattrib: grouping DXF attribute like ``'layer'``
        key: key function, which accepts a :class:`DXFEntity` as argument and returns a hashable grouping key
             or ``None`` to ignore this entity.

    """
    if all((dxfattrib, key)):
        raise DXFValueError(
            'Specify a dxfattrib or a key function, but not both.')
    if dxfattrib != '':
        key = lambda entity: entity.get_dxf_attrib(dxfattrib, None)
    if key is None:
        raise DXFValueError(
            'no valid argument found, specify a dxfattrib or a key function, but not both.'
        )

    result = dict()
    for dxf_entity in entities:
        if not dxf_entity.is_alive:
            continue
        try:
            group_key = key(dxf_entity)
        except DXFAttributeError:  # ignore DXF entities, which do not support all query attributes
            continue
        if group_key is not None:
            group = result.setdefault(group_key, [])
            group.append(dxf_entity)
    return result
Пример #22
0
 def get(self, appid: str) -> Tags:
     if appid in self.data:
         return self.data[appid]
     else:
         raise DXFValueError(appid)
Пример #23
0
 def add_edge(self, vertices: Sequence[Sequence[float]]) -> Sequence[int]:
     if len(vertices) != 2:
         raise DXFValueError(
             "Parameter vertices has to be a list/tuple of 2 vertices [(x1, y1, z1), (x2, y2, z2)]."
         )
     return self.add_entity(vertices, self.edges)
 def layers(self, names: Iterable[str]):
     if isinstance(names, str):
         raise DXFValueError('requires iterable but not string')
     self.layers[:] = list(names)
Пример #25
0
    def add_foreign_entity(self, entity: "DXFGraphic", copy=True) -> None:
        """Add a foreign DXF entity to a layout, this foreign entity could be
        from another DXF document or an entity without an assigned DXF document.
        The intention of this method is to add **simple** entities from another
        DXF document or from a DXF iterator, for more complex operations use the
        :mod:`~ezdxf.addons.importer` add-on. Especially objects with BLOCK
        section (INSERT, DIMENSION, MLEADER) or OBJECTS section dependencies
        (IMAGE, UNDERLAY) can not be supported by this simple method.

        Not all DXF types are supported and every dependency or resource
        reference from another DXF document will be removed except attribute
        layer will be preserved but only with default attributes like
        color ``7`` and linetype ``CONTINUOUS`` because the layer attribute
        doesn't need a layer table entry.

        If the entity is part of another DXF document, it will be unlinked from
        this document and its entity database if argument `copy` is ``False``,
        else the entity will be copied. Unassigned entities like from DXF
        iterators will just be added.

        Supported DXF types:

            - POINT
            - LINE
            - CIRCLE
            - ARC
            - ELLIPSE
            - LWPOLYLINE
            - SPLINE
            - POLYLINE
            - 3DFACE
            - SOLID
            - TRACE
            - SHAPE
            - MESH
            - ATTRIB
            - ATTDEF
            - TEXT
            - MTEXT
            - HATCH

        Args:
            entity: DXF entity to copy or move
            copy: if ``True`` copy entity from other document else unlink from
                other document

        """
        foreign_doc = entity.doc
        dxftype = entity.dxftype()
        if dxftype not in SUPPORTED_FOREIGN_ENTITY_TYPES:
            raise DXFTypeError(f"unsupported DXF type: {dxftype}")
        if foreign_doc is self.doc:
            raise DXFValueError("entity from same DXF document")

        if foreign_doc is not None:
            if copy:
                entity = entity.copy()
            else:
                # Unbind entity from other document without destruction.
                factory.unbind(entity)

        entity.remove_dependencies(self.doc)
        # add to this layout & bind to document
        self.add_entity(entity)
Пример #26
0
 def edit_pattern(self):
     if self.has_solid_fill:
         raise DXFValueError('Solid fill HATCH has no pattern data.')
     pattern_data = PatternData(self)
     yield pattern_data
     self._set_pattern_data(pattern_data)
Пример #27
0
 def edit_gradient(self):
     if not self.has_gradient_data:
         raise DXFValueError('HATCH has no gradient data.')
     gradient_data = GradientData.from_tags(self.AcDbHatch)
     yield gradient_data
     self._set_gradient(gradient_data)
Пример #28
0
 def get_gradient(self):
     if not self.has_gradient_data:
         raise DXFValueError('HATCH has no gradient data.')
     return GradientData.from_tags(self.AcDbHatch)
Пример #29
0
 def __setitem__(self, key: str, value: Any) -> None:
     try:
         tags = self._headervar_factory(key, value)
     except (IndexError, ValueError):
         raise DXFValueError(str(value))
     self.hdrvars[key] = HeaderVar(tags)
Пример #30
0
    def page_setup(self,
                   size=(297, 210),
                   margins=(0, 0, 0, 0),
                   units='mm',
                   offset=(0, 0),
                   rotation=0,
                   scale=16):
        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