1# Copyright (c) 2013-2021, Manfred Moitzi
2# License: MIT License
3from typing import TYPE_CHECKING, Iterable, Sequence, Dict, Tuple, cast
4import math
5import logging
6import warnings
7from ezdxf.lldxf import const
8from ezdxf.lldxf.const import DXFValueError, DXFVersionError, DXF2000, DXF2007
9from ezdxf.math import Vec3, global_bspline_interpolation, fit_points_to_cad_cv
10from ezdxf.render.arrows import ARROWS
11from ezdxf.entities import factory
12from ezdxf.entities.mtext_columns import *
13from ezdxf.entities.dimstyleoverride import DimStyleOverride
14from ezdxf.render.dim_linear import multi_point_linear_dimension
15
16logger = logging.getLogger('ezdxf')
17
18if TYPE_CHECKING:
19    from ezdxf.eztypes import (
20        UCS, Vertex, Drawing, DXFGraphic, Line, Arc, Circle, Point, Polyline,
21        Shape, Solid, Trace, Face3d, Insert, Attrib, Polyface, Polymesh, Text,
22        LWPolyline, Ellipse, MText, XLine, Ray, Spline, Leader, AttDef, Mesh,
23        Hatch, Image, ImageDef, Underlay, UnderlayDefinition, Body, Region,
24        Solid3d, LoftedSurface, Surface, RevolvedSurface, ExtrudedSurface,
25        SweptSurface, Wipeout, MLine,
26    )
27
28
29class CreatorInterface:
30    def __init__(self, doc: 'Drawing'):
31        self.doc = doc
32
33    @property
34    def dxfversion(self) -> str:
35        return self.doc.dxfversion
36
37    @property
38    def is_active_paperspace(self):
39        return False
40
41    def new_entity(self, type_: str, dxfattribs: Dict) -> 'DXFGraphic':
42        """
43        Create entity in drawing database and add entity to the entity space.
44
45        Args:
46            type_ : DXF type string, like ``'LINE'``, ``'CIRCLE'`` or
47                ``'LWPOLYLINE'``
48            dxfattribs: DXF attributes for the new entity
49
50        """
51        entity = factory.create_db_entry(type_, dxfattribs, self.doc)
52        self.add_entity(entity)
53        return entity
54
55    def add_entity(self, entity: 'DXFGraphic') -> None:
56        pass
57
58    def add_point(self, location: 'Vertex', dxfattribs: Dict = None) -> 'Point':
59        """
60        Add a :class:`~ezdxf.entities.Point` entity at `location`.
61
62        Args:
63            location: 2D/3D point in :ref:`WCS`
64            dxfattribs: additional DXF attributes
65
66        """
67        dxfattribs = dict(dxfattribs or {})
68        dxfattribs['location'] = Vec3(location)
69        return self.new_entity('POINT', dxfattribs)
70
71    def add_line(self, start: 'Vertex', end: 'Vertex',
72                 dxfattribs: Dict = None) -> 'Line':
73        """
74        Add a :class:`~ezdxf.entities.Line` entity from `start` to `end`.
75
76        Args:
77            start: 2D/3D point in :ref:`WCS`
78            end: 2D/3D point in :ref:`WCS`
79            dxfattribs: additional DXF attributes
80
81        """
82        dxfattribs = dict(dxfattribs or {})
83        dxfattribs['start'] = Vec3(start)
84        dxfattribs['end'] = Vec3(end)
85        return self.new_entity('LINE', dxfattribs)
86
87    def add_circle(self, center: 'Vertex', radius: float,
88                   dxfattribs: Dict = None) -> 'Circle':
89        """
90        Add a :class:`~ezdxf.entities.Circle` entity. This is an 2D element,
91        which can be placed in space by using :ref:`OCS`.
92
93        Args:
94            center: 2D/3D point in :ref:`WCS`
95            radius: circle radius
96            dxfattribs: additional DXF attributes
97
98        """
99        dxfattribs = dict(dxfattribs or {})
100        dxfattribs['center'] = Vec3(center)
101        dxfattribs['radius'] = float(radius)
102        return self.new_entity('CIRCLE', dxfattribs)
103
104    def add_ellipse(self, center: 'Vertex', major_axis: 'Vertex' = (1, 0, 0),
105                    ratio: float = 1, start_param: float = 0,
106                    end_param: float = math.tau,
107                    dxfattribs: Dict = None) -> 'Ellipse':
108        """
109        Add an :class:`~ezdxf.entities.Ellipse` entity, `ratio` is the ratio of
110        minor axis to major axis, `start_param` and `end_param` defines start
111        and end point of the ellipse, a full ellipse goes from 0 to 2*pi.
112        The ellipse goes from start to end param in `counter clockwise`
113        direction.
114
115        Args:
116            center: center of ellipse as 2D/3D point in :ref:`WCS`
117            major_axis: major axis as vector (x, y, z)
118            ratio: ratio of minor axis to major axis in range +/-[1e-6, 1.0]
119            start_param: start of ellipse curve
120            end_param: end param of ellipse curve
121            dxfattribs: additional DXF attributes
122
123        """
124        if self.dxfversion < DXF2000:
125            raise DXFVersionError('ELLIPSE requires DXF R2000')
126        dxfattribs = dict(dxfattribs or {})
127        dxfattribs['center'] = Vec3(center)
128        dxfattribs['major_axis'] = Vec3(major_axis)
129        dxfattribs['ratio'] = float(ratio)
130        dxfattribs['start_param'] = float(start_param)
131        dxfattribs['end_param'] = float(end_param)
132        return self.new_entity('ELLIPSE', dxfattribs)
133
134    def add_arc(self, center: 'Vertex', radius: float, start_angle: float,
135                end_angle: float,
136                is_counter_clockwise: bool = True,
137                dxfattribs: Dict = None) -> 'Arc':
138        """
139        Add an :class:`~ezdxf.entities.Arc` entity. The arc goes from
140        `start_angle` to `end_angle` in counter clockwise direction by default,
141        set parameter `is_counter_clockwise` to False for clockwise orientation.
142
143        Args:
144            center: center of arc as 2D/3D point in :ref:`WCS`
145            radius: arc radius
146            start_angle: start angle in degrees
147            end_angle: end angle in degrees
148            is_counter_clockwise: False for clockwise orientation
149            dxfattribs: additional DXF attributes
150
151        """
152        dxfattribs = dict(dxfattribs or {})
153        dxfattribs['center'] = Vec3(center)
154        dxfattribs['radius'] = float(radius)
155        if is_counter_clockwise:
156            dxfattribs['start_angle'] = float(start_angle)
157            dxfattribs['end_angle'] = float(end_angle)
158        else:
159            dxfattribs['start_angle'] = float(end_angle)
160            dxfattribs['end_angle'] = float(start_angle)
161        return self.new_entity('ARC', dxfattribs)
162
163    def add_solid(self, points: Iterable['Vertex'],
164                  dxfattribs: Dict = None) -> 'Solid':
165        """
166        Add a :class:`~ezdxf.entities.Solid` entity, `points` is an iterable
167        of 3 or 4 points.
168
169        Args:
170            points: iterable of 3 or 4 2D/3D points in :ref:`WCS`
171            dxfattribs: additional DXF attributes for :class:`Solid` entity
172
173        """
174        return cast('Solid',
175                    self._add_quadrilateral('SOLID', points, dxfattribs))
176
177    def add_trace(self, points: Iterable['Vertex'],
178                  dxfattribs: Dict = None) -> 'Trace':
179        """
180        Add a :class:`~ezdxf.entities.Trace` entity, `points` is an iterable
181        of 3 or 4 points.
182
183        Args:
184            points: iterable of 3 or 4 2D/3D points in :ref:`WCS`
185            dxfattribs: additional DXF attributes for :class:`Trace`
186                entity
187
188        """
189        return cast('Trace',
190                    self._add_quadrilateral('TRACE', points, dxfattribs))
191
192    def add_3dface(self, points: Iterable['Vertex'],
193                   dxfattribs: Dict = None) -> 'Face3d':
194        """
195        Add a :class:`~ezdxf.entities.3DFace` entity, `points` is an iterable
196        3 or 4 2D/3D points.
197
198        Args:
199            points: iterable of 3 or 4 2D/3D points in :ref:`WCS`
200            dxfattribs: additional DXF attributes for :class:`3DFace` entity
201
202        """
203        return cast('Face',
204                    self._add_quadrilateral('3DFACE', points, dxfattribs))
205
206    def add_text(self, text: str, dxfattribs: Dict = None) -> 'Text':
207        """
208        Add a :class:`~ezdxf.entities.Text` entity, see also :class:`Style`.
209
210        Args:
211            text: content string
212            dxfattribs: additional DXF attributes for :class:`Text` entity
213
214        """
215        dxfattribs = dict(dxfattribs or {})
216        dxfattribs['text'] = str(text)
217        dxfattribs.setdefault('insert', Vec3())
218        return self.new_entity('TEXT', dxfattribs)
219
220    def add_blockref(self, name: str, insert: 'Vertex',
221                     dxfattribs: Dict = None) -> 'Insert':
222        """
223        Add an :class:`~ezdxf.entities.Insert` entity.
224
225        When inserting a block reference into the modelspace or another block
226        layout with different units, the scaling factor between these units
227        should be applied as scaling attributes (:attr:`xscale`, ...) e.g.
228        modelspace in meters and block in centimeters, :attr:`xscale` has to
229        be 0.01.
230
231        Args:
232            name: block name as str
233            insert: insert location as 2D/3D point in :ref:`WCS`
234            dxfattribs: additional DXF attributes for :class:`Insert` entity
235
236        """
237        if not isinstance(name, str):
238            raise DXFValueError('Block name as string required.')
239
240        dxfattribs = dict(dxfattribs or {})
241        dxfattribs['name'] = name
242        dxfattribs['insert'] = Vec3(insert)
243        blockref = self.new_entity('INSERT', dxfattribs)  # type: Insert
244        return blockref
245
246    def add_auto_blockref(
247            self, name: str, insert: 'Vertex', values: Dict[str, str],
248            dxfattribs: Dict = None) -> 'Insert':
249        """
250        Add an :class:`~ezdxf.entities.Insert` entity. This method adds for each
251        :class:`~ezdxf.entities.Attdef` entity, defined in the block definition,
252        automatically an :class:`Attrib` entity to the block reference and set
253        ``tag/value`` DXF attributes of the ATTRIB entities by the ``key/value``
254        pairs (both as strings) of the `values` dict.
255
256        The Attrib entities are placed relative to the insert point, which is
257        equal to the block base point.
258
259        This method wraps the INSERT and all the ATTRIB entities into an
260        anonymous block, which produces the best visual results, especially for
261        non uniform scaled block references, because the transformation and
262        scaling is done by the CAD application. But this makes evaluation of
263        block references with attributes more complicated, if you prefer INSERT
264        and ATTRIB entities without a wrapper block use the
265        :meth:`add_blockref_with_attribs` method.
266
267        Args:
268            name: block name
269            insert: insert location as 2D/3D point in :ref:`WCS`
270            values: :class:`~ezdxf.entities.Attrib` tag values as ``tag/value`` pairs
271            dxfattribs: additional DXF attributes for :class:`Insert` entity
272
273        """
274        if not isinstance(name, str):
275            raise DXFValueError('Block name as string required.')
276
277        def unpack(dxfattribs) -> Tuple[str, str, 'Vertex']:
278            tag = dxfattribs.pop('tag')
279            text = values.get(tag, "")
280            location = dxfattribs.pop('insert')
281            return tag, text, location
282
283        def autofill() -> None:
284            # ATTRIBs are placed relative to the base point
285            for attdef in blockdef.attdefs():
286                dxfattribs = attdef.dxfattribs(drop={'prompt', 'handle'})
287                tag, text, location = unpack(dxfattribs)
288                blockref.add_attrib(tag, text, location, dxfattribs)
289
290        dxfattribs = dict(dxfattribs or {})
291        autoblock = self.doc.blocks.new_anonymous_block()
292        blockref = autoblock.add_blockref(name, (0, 0))
293        blockdef = self.doc.blocks[name]
294        autofill()
295        return self.add_blockref(autoblock.name, insert, dxfattribs)
296
297    def add_attrib(self, tag: str, text: str, insert: 'Vertex' = (0, 0),
298                   dxfattribs: Dict = None) -> 'Attrib':
299        """
300        Add an :class:`~ezdxf.entities.Attrib` as stand alone DXF entity.
301
302        Args:
303            tag: tag name as string
304            text: tag value as string
305            insert: insert location as 2D/3D point in :ref:`WCS`
306            dxfattribs: additional DXF attributes for :class:`Attrib` entity
307
308        """
309        dxfattribs = dict(dxfattribs or {})
310        dxfattribs['tag'] = str(tag)
311        dxfattribs['text'] = str(text)
312        dxfattribs['insert'] = Vec3(insert)
313        return self.new_entity('ATTRIB', dxfattribs)
314
315    def add_attdef(self, tag: str, insert: 'Vertex' = (0, 0), text: str = '',
316                   dxfattribs: Dict = None) -> 'AttDef':
317        """
318        Add an :class:`~ezdxf.entities.AttDef` as stand alone DXF entity.
319
320        Set position and alignment by the idiom::
321
322            layout.add_attdef('NAME').set_pos((2, 3), align='MIDDLE_CENTER')
323
324        Args:
325            tag: tag name as string
326            insert: insert location as 2D/3D point in :ref:`WCS`
327            text: tag value as string
328            dxfattribs: additional DXF attributes
329
330        """
331        dxfattribs = dict(dxfattribs or {})
332        dxfattribs['tag'] = str(tag)
333        dxfattribs['insert'] = Vec3(insert)
334        dxfattribs['text'] = str(text)
335        return self.new_entity('ATTDEF', dxfattribs)
336
337    def add_polyline2d(self, points: Iterable['Vertex'],
338                       format: str = None,
339                       *,
340                       close: bool = False,
341                       dxfattribs: Dict = None) -> 'Polyline':
342        """
343        Add a 2D :class:`~ezdxf.entities.Polyline` entity.
344
345        Args:
346            points: iterable of 2D points in :ref:`WCS`
347            close: `True` for a closed polyline
348            format: user defined point format like :meth:`add_lwpolyline`,
349                default is ``None``
350            dxfattribs: additional DXF attributes
351
352        """
353        dxfattribs = dict(dxfattribs or {})
354        if 'closed' in dxfattribs:
355            warnings.warn('dxfattribs key "closed" is deprecated, '
356                          'use keyword argument "close"', DeprecationWarning)
357
358        close = dxfattribs.pop('closed', close)
359        polyline: 'Polyline' = self.new_entity('POLYLINE', dxfattribs)
360        polyline.close(close)
361        if format is not None:
362            polyline.append_formatted_vertices(points, format=format)
363        else:
364            polyline.append_vertices(points)
365        if self.doc:
366            polyline.add_sub_entities_to_entitydb(self.doc.entitydb)
367        return polyline
368
369    def add_polyline3d(self, points: Iterable['Vertex'],
370                       *,
371                       close: bool = False,
372                       dxfattribs: Dict = None) -> 'Polyline':
373        """ Add a 3D :class:`~ezdxf.entities.Polyline` entity.
374
375        Args:
376            points: iterable of 3D points in :ref:`WCS`
377            close: `True` for a closed polyline
378            dxfattribs: additional DXF attributes
379
380        """
381        dxfattribs = dict(dxfattribs or {})
382        dxfattribs['flags'] = dxfattribs.get('flags',
383                                             0) | const.POLYLINE_3D_POLYLINE
384        return self.add_polyline2d(points, close=close, dxfattribs=dxfattribs)
385
386    def add_polymesh(self, size: Tuple[int, int] = (3, 3),
387                     dxfattribs: Dict = None) -> 'Polymesh':
388        """
389        Add a :class:`~ezdxf.entities.Polymesh` entity, which is a wrapper class
390        for the POLYLINE entity. A polymesh is a grid of `mcount` x `ncount`
391        vertices and every vertex has its own (x, y, z)-coordinates.
392
393        Args:
394            size: 2-tuple (`mcount`, `ncount`)
395            dxfattribs: additional DXF attributes for
396                :class:`~ezdxf.entities.Polyline` entity
397
398        """
399        dxfattribs = dict(dxfattribs or {})
400        dxfattribs['flags'] = dxfattribs.get('flags',
401                                             0) | const.POLYLINE_3D_POLYMESH
402        m_size = max(size[0], 2)
403        n_size = max(size[1], 2)
404        dxfattribs['m_count'] = m_size
405        dxfattribs['n_count'] = n_size
406        m_close = dxfattribs.pop('m_close', False)
407        n_close = dxfattribs.pop('n_close', False)
408        # returns casted entity
409        polymesh = self.new_entity('POLYLINE', dxfattribs)  # type: Polymesh
410
411        points = [(0, 0, 0)] * (m_size * n_size)
412        polymesh.append_vertices(points)  # init mesh vertices
413        polymesh.close(m_close, n_close)
414        if self.doc:
415            polymesh.add_sub_entities_to_entitydb(self.doc.entitydb)
416
417        return polymesh
418
419    def add_polyface(self, dxfattribs: Dict = None) -> 'Polyface':
420        """ Add a :class:`~ezdxf.entities.Polyface` entity, which is a wrapper
421        class for the POLYLINE entity.
422
423        Args:
424            dxfattribs: additional DXF attributes for
425                :class:`~ezdxf.entities.Polyline` entity
426
427        """
428        dxfattribs = dict(dxfattribs or {})
429        dxfattribs['flags'] = dxfattribs.get('flags',
430                                             0) | const.POLYLINE_POLYFACE
431        m_close = dxfattribs.pop('m_close', False)
432        n_close = dxfattribs.pop('n_close', False)
433        polyface = self.new_entity('POLYLINE', dxfattribs)  # type: Polyface
434        polyface.close(m_close, n_close)
435        if self.doc:
436            polyface.add_sub_entities_to_entitydb(self.doc.entitydb)
437
438        return polyface
439
440    def _add_quadrilateral(self, type_: str, points: Iterable['Vertex'],
441                           dxfattribs: Dict = None) -> 'DXFGraphic':
442        dxfattribs = dict(dxfattribs or {})
443        entity = self.new_entity(type_, dxfattribs)
444        for x, point in enumerate(self._four_points(points)):
445            entity[x] = Vec3(point)
446        return entity
447
448    @staticmethod
449    def _four_points(points: Iterable['Vertex']) -> Iterable['Vertex']:
450        vertices = list(points)
451        if len(vertices) not in (3, 4):
452            raise DXFValueError('3 or 4 points required.')
453        for vertex in vertices:
454            yield vertex
455        if len(vertices) == 3:
456            yield vertices[-1]  # last again
457
458    def add_shape(self, name: str, insert: 'Vertex' = (0, 0), size: float = 1.0,
459                  dxfattribs: Dict = None) -> 'Shape':
460        """
461        Add a :class:`~ezdxf.entities.Shape` reference to a external stored shape.
462
463        Args:
464            name: shape name as string
465            insert: insert location as 2D/3D point in :ref:`WCS`
466            size: size factor
467            dxfattribs: additional DXF attributes
468
469        """
470        dxfattribs = dict(dxfattribs or {})
471        dxfattribs['name'] = str(name)
472        dxfattribs['insert'] = Vec3(insert)
473        dxfattribs['size'] = float(size)
474        return self.new_entity('SHAPE', dxfattribs)
475
476    # new entities in DXF AC1015 (R2000)
477
478    def add_lwpolyline(self, points: Iterable['Vertex'],
479                       format: str = 'xyseb',
480                       *,
481                       close: bool = False,
482                       dxfattribs: Dict = None) -> 'LWPolyline':
483        """
484        Add a 2D polyline as :class:`~ezdxf.entities.LWPolyline` entity.
485        A points are defined as (x, y, [start_width, [end_width, [bulge]]])
486        tuples, but order can be redefined by the `format` argument. Set
487        `start_width`, `end_width` to ``0`` to be ignored like
488        (x, y, 0, 0, bulge).
489
490        The :class:`~ezdxf.entities.LWPolyline` is defined as a single DXF
491        entity and needs less disk space than a
492        :class:`~ezdxf.entities.Polyline` entity. (requires DXF R2000)
493
494        Format codes:
495
496            - ``x`` = x-coordinate
497            - ``y`` = y-coordinate
498            - ``s`` = start width
499            - ``e`` = end width
500            - ``b`` = bulge value
501            - ``v`` = (x, y [,z]) tuple (z-axis is ignored)
502
503        Args:
504            points: iterable of (x, y, [start_width, [end_width, [bulge]]]) tuples
505            format: user defined point format, default is ``"xyseb"``
506            close: `True` for a closed polyline
507            dxfattribs: additional DXF attributes
508
509        """
510        if self.dxfversion < DXF2000:
511            raise DXFVersionError('LWPOLYLINE requires DXF R2000')
512        dxfattribs = dict(dxfattribs or {})
513        if 'closed' in dxfattribs:
514            warnings.warn('dxfattribs key "closed" is deprecated, '
515                          'use keyword argument "close"', DeprecationWarning)
516        close = dxfattribs.pop('closed', close)
517        lwpolyline: 'LWPolyline' = self.new_entity('LWPOLYLINE', dxfattribs)
518        lwpolyline.set_points(points, format=format)
519        lwpolyline.closed = close
520        return lwpolyline
521
522    def add_mtext(self, text: str, dxfattribs: Dict = None) -> 'MText':
523        """
524        Add a multiline text entity with automatic text wrapping at boundaries
525        as :class:`~ezdxf.entities.MText` entity.
526        (requires DXF R2000)
527
528        Args:
529            text: content string
530            dxfattribs: additional DXF attributes
531
532        """
533        if self.dxfversion < DXF2000:
534            raise DXFVersionError('MTEXT requires DXF R2000')
535        dxfattribs = dict(dxfattribs or {})
536        mtext: 'MText' = self.new_entity('MTEXT', dxfattribs)
537        mtext.text = str(text)
538        return mtext
539
540    def add_mtext_static_columns(
541            self, content: Iterable[str],
542            width: float, gutter_width: float, height: float,
543            dxfattribs: Dict = None) -> 'MText':
544        """ Add a multiline text entity with static columns as
545        :class:`~ezdxf.entities.MText` entity. The content is spread
546        across the columns, the count of content strings determine the count
547        of columns.
548
549        This factory method adds automatically a column break ``\\N`` at the
550        end of each column text to force a new column.
551        The `height` attribute should be big enough to reserve enough space for
552        the tallest column. Too small values produce valid DXF files, but the
553        visual result will not be as expected. The `height` attribute also
554        defines the total height of the MTEXT entity.
555
556        (requires DXF R2000)
557
558        Args:
559            content: iterable of column content
560            width: column width
561            gutter_width: distance between columns
562            height: max. column height
563            dxfattribs: additional DXF attributes
564
565        .. versionadded:: 0.17
566
567        """
568        dxfversion = self.dxfversion
569        if dxfversion < DXF2000:
570            raise DXFVersionError('MTEXT requires DXF R2000')
571        content = list(content)
572        if dxfversion < const.DXF2018:
573            mtext = make_static_columns_r2000(
574                content, width, gutter_width, height, dxfattribs)
575        else:
576            mtext = make_static_columns_r2018(
577                content, width, gutter_width, height, dxfattribs)
578        if self.doc:
579            self.doc.entitydb.add(mtext)
580        self.add_entity(mtext)
581        return mtext
582
583    def add_mtext_dynamic_auto_height_columns(
584            self, content: str,
585            width: float, gutter_width: float, height: float, count: int,
586            dxfattribs: Dict = None) -> 'MText':
587        """ DO NOT USE THIS INTERFACE IN PRODUCTION CODE!
588
589        Without a usable MTEXT rendering engine is this interface useless,
590        the main goal is to get the column count from the content, which
591        requires an exact MTEXT rendering like AutoCAD/BricsCAD.
592
593        (requires DXF R2000)
594
595        Args:
596            content: column content as a single string
597            width: column width
598            gutter_width: distance between columns
599            height: max. column height
600            count: expected column count
601            dxfattribs: additional DXF attributes
602
603        .. versionadded:: 0.17
604
605        """
606        dxfversion = self.dxfversion
607        if dxfversion < DXF2000:
608            raise DXFVersionError('MTEXT requires DXF R2000')
609        if dxfversion < const.DXF2018:
610            mtext = make_dynamic_auto_height_columns_r2000(
611                content, width, gutter_width, height, count, dxfattribs)
612        else:
613            mtext = make_dynamic_auto_height_columns_r2018(
614                content, width, gutter_width, height, count, dxfattribs)
615        if self.doc:
616            self.doc.entitydb.add(mtext)
617        self.add_entity(mtext)
618        return mtext
619
620    def add_mtext_dynamic_manual_height_columns(
621            self, content: str,
622            width: float, gutter_width: float, heights: Sequence[float],
623            dxfattribs: Dict = None) -> 'MText':
624        """ Add a multiline text entity with dynamic columns as
625        :class:`~ezdxf.entities.MText` entity. The content is spread
626        across the columns automatically by the CAD application.
627        The `heights` sequence determine the height of the columns, except for
628        the last column, which always takes the remaining content. The height
629        value for the last column is required but can be 0, because the value
630        is ignored. The count of `heights` also determines the count of columns,
631        and :code:`max(heights)` defines the total height of the MTEXT entity,
632        which may be wrong if the last column requires more space.
633
634        (requires DXF R2000)
635
636        Args:
637            content: column content as a single string
638            width: column width
639            gutter_width: distance between columns
640            heights: column height for each column
641            dxfattribs: additional DXF attributes
642
643        .. versionadded:: 0.17
644
645        """
646        # The current implementation work well for R2018.
647        #
648        # For the prior DXF versions the current implementation puts the whole
649        # content into the first (main) column.
650        # This works for AutoCAD and BricsCAD, both collect the content from
651        # the linked MTEXT columns and the main column and do their own MTEXT
652        # rendering - DO trust the DXF content!
653        # The drawing add-on will fail at this until a usable MTEXT renderer
654        # is implemented. For now the drawing add-on renders the main MTEXT and
655        # the linked MTEXT columns as standalone MTEXT entities, and all content
656        # is stored in the main MTEXT entity.
657        # For the same reason the R2018 MTEXT rendering is not correct.
658        # In DXF R2018 the MTEXT entity is a single entity without linked
659        # MTEXT columns and the content is a single string, which has to be
660        # parsed, rendered and distributed across the columns by the
661        # CAD application itself.
662
663        dxfversion = self.dxfversion
664        if dxfversion < DXF2000:
665            raise DXFVersionError('MTEXT requires DXF R2000')
666        if dxfversion < const.DXF2018:
667            mtext = make_dynamic_manual_height_columns_r2000(
668                content, width, gutter_width, heights, dxfattribs)
669        else:
670            mtext = make_dynamic_manual_height_columns_r2018(
671                content, width, gutter_width, heights, dxfattribs)
672        if self.doc:
673            self.doc.entitydb.add(mtext)
674        self.add_entity(mtext)
675        return mtext
676
677    def add_ray(self, start: 'Vertex', unit_vector: 'Vertex',
678                dxfattribs: Dict = None) -> 'Ray':
679        """
680        Add a :class:`~ezdxf.entities.Ray` that begins at `start` point and
681        continues to infinity (construction line). (requires DXF R2000)
682
683        Args:
684            start: location 3D point in :ref:`WCS`
685            unit_vector: 3D vector (x, y, z)
686            dxfattribs: additional DXF attributes
687
688        """
689        if self.dxfversion < DXF2000:
690            raise DXFVersionError('RAY requires DXF R2000')
691        dxfattribs = dict(dxfattribs or {})
692        dxfattribs['start'] = Vec3(start)
693        dxfattribs['unit_vector'] = Vec3(unit_vector).normalize()
694        return self.new_entity('RAY', dxfattribs)
695
696    def add_xline(self, start: 'Vertex', unit_vector: 'Vertex',
697                  dxfattribs: Dict = None) -> 'XLine':
698        """ Add an infinity :class:`~ezdxf.entities.XLine` (construction line).
699        (requires DXF R2000)
700
701        Args:
702            start: location 3D point in :ref:`WCS`
703            unit_vector: 3D vector ``(x, y, z)``
704            dxfattribs: additional DXF attributes
705
706        """
707        if self.dxfversion < DXF2000:
708            raise DXFVersionError('XLINE requires DXF R2000')
709        dxfattribs = dict(dxfattribs or {})
710        dxfattribs['start'] = Vec3(start)
711        dxfattribs['unit_vector'] = Vec3(unit_vector).normalize()
712        return self.new_entity('XLINE', dxfattribs)
713
714    def add_spline(self, fit_points: Iterable['Vertex'] = None, degree: int = 3,
715                   dxfattribs: Dict = None) -> 'Spline':
716        """ Add a B-spline (:class:`~ezdxf.entities.Spline` entity) defined by
717        the given `fit_points` - the control points and knot values are created
718        by the CAD application, therefore it is not predictable how the rendered
719        spline will look like, because for every set of fit points exists an
720        infinite set of B-splines.
721
722        If `fit_points` is ``None``, an "empty" spline will be created, all data
723        has to be set by the user.
724
725        The SPLINE entity requires DXF R2000.
726
727        AutoCAD creates a spline through fit points by a global curve
728        interpolation and an unknown method to estimate the direction of the
729        start- and end tangent.
730
731        .. seealso::
732
733            - :ref:`tut_spline`
734            - :func:`ezdxf.math.fit_points_to_cad_cv`
735
736        Args:
737            fit_points: iterable of fit points as ``(x, y[, z])`` in :ref:`WCS`,
738                creates an empty :class:`~ezdxf.entities.Spline` if ``None``
739            degree: degree of B-spline, max. degree supported by AutoCAD is 11
740            dxfattribs: additional DXF attributes
741
742        """
743        if self.dxfversion < DXF2000:
744            raise DXFVersionError('SPLINE requires DXF R2000')
745        dxfattribs = dict(dxfattribs or {})
746        dxfattribs['degree'] = int(degree)
747        spline = self.new_entity('SPLINE', dxfattribs)
748        if fit_points is not None:
749            spline.fit_points = Vec3.generate(fit_points)
750        return spline
751
752    def add_spline_control_frame(self, fit_points: Iterable['Vertex'],
753                                 degree: int = 3, method: str = 'chord',
754                                 dxfattribs: Dict = None) -> 'Spline':
755        """ Add a :class:`~ezdxf.entities.Spline` entity passing through the
756        given `fit_points`, the control points are calculated by a global curve
757        interpolation without start- and end tangent constrains.
758        The new SPLINE entity is defined by control points and not by the fit
759        points, therefore the SPLINE looks always the same, no matter which CAD
760        application renders the SPLINE.
761
762        - "uniform": creates a uniform t vector, from 0 to 1 evenly spaced, see
763          `uniform`_ method
764        - "distance", "chord": creates a t vector with values proportional to
765          the fit point distances, see `chord length`_ method
766        - "centripetal", "sqrt_chord": creates a t vector with values
767          proportional to the fit point sqrt(distances), see `centripetal`_
768          method
769        - "arc": creates a t vector with values proportional to the arc length
770          between fit points.
771
772        Use function :meth:`add_cad_spline_control_frame` to create
773        SPLINE entities from fit points similar to CAD application including
774        start- and end tangent constraints.
775
776        Args:
777            fit_points: iterable of fit points as (x, y[, z]) in :ref:`WCS`
778            degree: degree of B-spline, max. degree supported by AutoCAD is 11
779            method: calculation method for parameter vector t
780            dxfattribs: additional DXF attributes
781
782        """
783        bspline = global_bspline_interpolation(fit_points, degree=degree,
784                                               method=method)
785        return self.add_open_spline(
786            control_points=bspline.control_points,
787            degree=bspline.degree,
788            knots=bspline.knots(),
789            dxfattribs=dxfattribs,
790        )
791
792    def add_cad_spline_control_frame(self, fit_points: Iterable['Vertex'],
793                                     tangents: Iterable['Vertex'] = None,
794                                     estimate: str = '5-p',
795                                     dxfattribs: Dict = None) -> 'Spline':
796        """ Add a :class:`~ezdxf.entities.Spline` entity passing through the
797        given fit points.
798        This method tries to create the same curve as CAD applications do.
799        To understand the limitations and for more information see function
800        :func:`ezdxf.math.fit_points_to_cad_cv`.
801
802        Args:
803            fit_points: iterable of fit points as (x, y[, z]) in :ref:`WCS`
804            tangents: start- and end tangent, default is autodetect
805            estimate: tangent direction estimation method
806            dxfattribs: additional DXF attributes
807
808        """
809        s = fit_points_to_cad_cv(
810            fit_points,
811            tangents=tangents,
812            estimate=estimate
813        )
814        spline = self.add_spline(dxfattribs=dxfattribs)
815        spline.apply_construction_tool(s)
816        return spline
817
818    def add_open_spline(self, control_points: Iterable['Vertex'],
819                        degree: int = 3, knots: Iterable[float] = None,
820                        dxfattribs: Dict = None) -> 'Spline':
821        """
822        Add an open uniform :class:`~ezdxf.entities.Spline` defined by
823        `control_points`. (requires DXF R2000)
824
825        Open uniform B-splines start and end at your first and last control point.
826
827        Args:
828            control_points: iterable of 3D points in :ref:`WCS`
829            degree: degree of B-spline, max. degree supported by AutoCAD is 11
830            knots: knot values as iterable of floats
831            dxfattribs: additional DXF attributes
832
833        """
834        spline = self.add_spline(dxfattribs=dxfattribs)
835        spline.set_open_uniform(list(control_points), degree)
836        if knots is not None:
837            spline.knots = knots
838        return spline
839
840    def add_rational_spline(self, control_points: Iterable['Vertex'],
841                            weights: Sequence[float], degree: int = 3,
842                            knots: Iterable[float] = None,
843                            dxfattribs: Dict = None) -> 'Spline':
844        """
845        Add an open rational uniform :class:`~ezdxf.entities.Spline` defined by
846        `control_points`. (requires DXF R2000)
847
848        `weights` has to be an iterable of floats, which defines the influence
849        of the associated control point to the shape of the B-spline, therefore
850        for each control point is one weight value required.
851
852        Open rational uniform B-splines start and end at the first and last
853        control point.
854
855        Args:
856            control_points: iterable of 3D points in :ref:`WCS`
857            weights: weight values as iterable of floats
858            degree: degree of B-spline, max. degree supported by AutoCAD is 11
859            knots: knot values as iterable of floats
860            dxfattribs: additional DXF attributes
861
862        """
863        spline = self.add_spline(dxfattribs=dxfattribs)
864        spline.set_open_rational(list(control_points), weights, degree)
865        if knots is not None:
866            spline.knots = knots
867        return spline
868
869    def add_body(self, acis_data: Iterable[str] = None,
870                 dxfattribs: Dict = None) -> 'Body':
871        """
872        Add a :class:`~ezdxf.entities.Body` entity. (requires DXF R2000-R2010)
873
874        The ACIS data has to be provided as an iterable of strings with no line
875        endings and only the SAT (text) format is supported, DXF R2013+
876        uses the SAB (binary) format which is not supported, and `ezdxf` has no
877        functionality to create ACIS data.
878
879        Args:
880            acis_data: ACIS data as iterable of text lines as strings, no
881                interpretation by ezdxf possible
882            dxfattribs: additional DXF attributes
883
884        """
885        return self._add_acis_entiy('BODY', acis_data, dxfattribs)
886
887    def add_region(self, acis_data: Iterable[str] = None,
888                   dxfattribs: Dict = None) -> 'Region':
889        """
890        Add a :class:`~ezdxf.entities.Region` entity. (requires DXF R2000-R2010)
891
892        The ACIS data has to be provided as an iterable of strings with no line
893        endings and only the SAT (text) format is supported, DXF R2013+
894        uses the SAB (binary) format which is not supported, and `ezdxf` has no
895        functionality to create ACIS data.
896
897        Args:
898            acis_data: ACIS data as iterable of text lines as strings,
899                no interpretation by ezdxf possible
900            dxfattribs: additional DXF attributes
901
902        """
903        return cast('Region',
904                    self._add_acis_entiy('REGION', acis_data, dxfattribs))
905
906    def add_3dsolid(self, acis_data: Iterable[str] = None,
907                    dxfattribs: Dict = None) -> 'Solid3d':
908        """
909        Add a 3DSOLID entity (:class:`~ezdxf.entities.Solid3d`).
910        (requires DXF R2000-R2010)
911
912        The ACIS data has to be provided as an iterable of strings with no line
913        endings and only the SAT (text) format is supported, DXF R2013+
914        uses the SAB (binary) format which is not supported, and `ezdxf` has no
915        functionality to create ACIS data.
916
917        Args:
918            acis_data: ACIS data as iterable of text lines as strings,
919                no interpretation by ezdxf possible
920            dxfattribs: additional DXF attributes
921
922        """
923        return cast('Solid3d',
924                    self._add_acis_entiy('3DSOLID', acis_data, dxfattribs))
925
926    def add_surface(self, acis_data: Iterable[str] = None,
927                    dxfattribs: Dict = None) -> 'Surface':
928        """
929        Add a :class:`~ezdxf.entities.Surface` entity. (requires DXF R2000-R2010)
930
931        The ACIS data has to be provided as an iterable of strings with no line
932        endings and only the SAT (text) format is supported, DXF R2013+
933        uses the SAB (binary) format which is not supported, and `ezdxf` has no
934        functionality to create ACIS data.
935
936        Args:
937            acis_data: ACIS data as iterable of text lines as strings,
938                no interpretation by ezdxf possible
939            dxfattribs: additional DXF attributes
940
941        """
942        if not (const.DXF2007 <= self.dxfversion <= const.DXF2010):
943            raise DXFVersionError('SURFACE requires DXF R2007-R2010')
944        return cast('Surface',
945                    self._add_acis_entiy('SURFACE', acis_data, dxfattribs))
946
947    def add_extruded_surface(self, acis_data: Iterable[str] = None,
948                             dxfattribs: Dict = None) -> 'ExtrudedSurface':
949        """
950        Add a :class:`~ezdxf.entities.ExtrudedSurface` entity.
951        (requires DXF R2000-R2010)
952
953        The ACIS data has to be provided as an iterable of strings with no line
954        endings and only the SAT (text) format is supported, DXF R2013+
955        uses the SAB (binary) format which is not supported, and `ezdxf` has no
956        functionality to create ACIS data.
957
958        Args:
959            acis_data: ACIS data as iterable of text lines as strings,
960                no interpretation by ezdxf possible
961            dxfattribs: additional DXF attributes
962
963        """
964        if not (const.DXF2007 <= self.dxfversion <= const.DXF2010):
965            raise DXFVersionError('EXTRUDEDSURFACE requires DXF R2007-R2010')
966        return cast('ExtrudedSurface',
967                    self._add_acis_entiy('EXTRUDEDSURFACE', acis_data,
968                                         dxfattribs))
969
970    def add_lofted_surface(self, acis_data: Iterable[str] = None,
971                           dxfattribs: Dict = None) -> 'LoftedSurface':
972        """
973        Add a :class:`~ezdxf.entities.LoftedSurface` entity.
974        (requires DXF R2007-R2010)
975
976        The ACIS data has to be provided as an iterable of strings with no line
977        endings and only the SAT (text) format is supported, DXF R2013+
978        uses the SAB (binary) format which is not supported, and `ezdxf` has no
979        functionality to create ACIS data.
980
981        Args:
982            acis_data: ACIS data as iterable of text lines as strings,
983                no interpretation by ezdxf possible
984            dxfattribs: additional DXF attributes
985
986        """
987        if not (const.DXF2007 <= self.dxfversion <= const.DXF2010):
988            raise DXFVersionError('LOFTEDSURFACE requires DXF R2007-R2010')
989        return cast('LoftedSurface',
990                    self._add_acis_entiy('LOFTEDSURFACE', acis_data,
991                                         dxfattribs))
992
993    def add_revolved_surface(self, acis_data: Iterable[str] = None,
994                             dxfattribs: Dict = None) -> 'RevolvedSurface':
995        """
996        Add a :class:`~ezdxf.entities.RevolvedSurface` entity.
997        (requires DXF R2007-R2010)
998
999        The ACIS data has to be provided as an iterable of strings with no line
1000        endings and only the SAT (text) format is supported, DXF R2013+
1001        uses the SAB (binary) format which is not supported, and `ezdxf` has no
1002        functionality to create ACIS data.
1003
1004        Args:
1005            acis_data: ACIS data as iterable of text lines as strings,
1006                no interpretation by ezdxf possible
1007            dxfattribs: additional DXF attributes
1008
1009        """
1010        if not (const.DXF2007 <= self.dxfversion <= const.DXF2010):
1011            raise DXFVersionError('REVOLVEDSURFACE requires DXF R2007-R2010')
1012        return cast('RevolvedSurface',
1013                    self._add_acis_entiy('REVOLVEDSURFACE', acis_data,
1014                                         dxfattribs))
1015
1016    def add_swept_surface(self, acis_data: Iterable[str] = None,
1017                          dxfattribs: Dict = None) -> 'SweptSurface':
1018        """
1019        Add a :class:`~ezdxf.entities.SweptSurface` entity.
1020        (requires DXF R2007-R2010)
1021
1022        The ACIS data has to be provided as an iterable of strings with no line
1023        endings and only the SAT (text) format is supported, DXF R2013+
1024        uses the SAB (binary) format which is not supported, and `ezdxf` has no
1025        functionality to create ACIS data.
1026
1027        Args:
1028            acis_data: ACIS data as iterable of text lines as strings,
1029                no interpretation by ezdxf possible
1030            dxfattribs: additional DXF attributes
1031
1032        """
1033        if not (const.DXF2007 <= self.dxfversion <= const.DXF2010):
1034            raise DXFVersionError('SWEPTSURFACE requires DXF R2007-R2010')
1035        return cast('SweptSurface',
1036                    self._add_acis_entiy('SWEPTSURFACE', acis_data, dxfattribs))
1037
1038    def _add_acis_entiy(self, name, acis_data: Iterable[str], dxfattribs: Dict) -> 'Body':
1039        if not (const.DXF2000 <= self.dxfversion <= const.DXF2010):
1040            raise DXFVersionError(f'{name} requires DXF R2000-R2010')
1041        dxfattribs = dict(dxfattribs or {})
1042        entity = cast('Body', self.new_entity(name, dxfattribs))
1043        if acis_data is not None:
1044            entity.acis_data = acis_data
1045        return entity
1046
1047    def add_hatch(self, color: int = 7, dxfattribs: Dict = None) -> 'Hatch':
1048        """
1049        Add a :class:`~ezdxf.entities.Hatch` entity. (requires DXF R2007)
1050
1051        Args:
1052            color: ACI (AutoCAD Color Index), default is ``7`` (black/white).
1053            dxfattribs: additional DXF attributes
1054
1055        """
1056        if self.dxfversion < DXF2000:
1057            raise DXFVersionError('HATCH requires DXF R2000')
1058        dxfattribs = dict(dxfattribs or {})
1059        dxfattribs['solid_fill'] = 1
1060        dxfattribs['color'] = int(color)
1061        dxfattribs['pattern_name'] = 'SOLID'
1062        return self.new_entity('HATCH', dxfattribs)
1063
1064    def add_mesh(self, dxfattribs: Dict = None) -> 'Mesh':
1065        """
1066        Add a :class:`~ezdxf.entities.Mesh` entity. (requires DXF R2007)
1067
1068        Args:
1069            dxfattribs: additional DXF attributes
1070
1071        """
1072        if self.dxfversion < DXF2000:
1073            raise DXFVersionError('MESH requires DXF R2000')
1074        dxfattribs = dict(dxfattribs or {})
1075        return self.new_entity('MESH', dxfattribs)
1076
1077    def add_image(self, image_def: 'ImageDef', insert: 'Vertex',
1078                  size_in_units: Tuple[float, float],
1079                  rotation: float = 0.,
1080                  dxfattribs: Dict = None) -> 'Image':
1081        """
1082        Add an :class:`~ezdxf.entities.Image` entity, requires a
1083        :class:`~ezdxf.entities.ImageDef` entity, see :ref:`tut_image`.
1084        (requires DXF R2000)
1085
1086        Args:
1087            image_def: required image definition as :class:`~ezdxf.entities.ImageDef`
1088            insert: insertion point as 3D point in :ref:`WCS`
1089            size_in_units: size as ``(x, y)`` tuple in drawing units
1090            rotation: rotation angle around the extrusion axis, default is the
1091                z-axis, in degrees
1092            dxfattribs: additional DXF attributes
1093
1094        """
1095
1096        def to_vector(units_per_pixel, angle_in_rad):
1097            x = math.cos(angle_in_rad) * units_per_pixel
1098            y = math.sin(angle_in_rad) * units_per_pixel
1099            # supports only images in the xy-plane:
1100            return Vec3(round(x, 6), round(y, 6), 0)
1101
1102        if self.dxfversion < DXF2000:
1103            raise DXFVersionError('IMAGE requires DXF R2000')
1104        dxfattribs = dict(dxfattribs or {})
1105        x_pixels, y_pixels = image_def.dxf.image_size.vec2
1106        x_units, y_units = size_in_units
1107        x_units_per_pixel = x_units / x_pixels
1108        y_units_per_pixel = y_units / y_pixels
1109        x_angle_rad = math.radians(rotation)
1110        y_angle_rad = x_angle_rad + (math.pi / 2.)
1111
1112        dxfattribs['insert'] = Vec3(insert)
1113        dxfattribs['u_pixel'] = to_vector(x_units_per_pixel, x_angle_rad)
1114        dxfattribs['v_pixel'] = to_vector(y_units_per_pixel, y_angle_rad)
1115        dxfattribs['image_def'] = image_def  # is not a real DXF attrib
1116        return cast('Image', self.new_entity('IMAGE', dxfattribs))
1117
1118    def add_wipeout(self, vertices: Iterable['Vertex'],
1119                    dxfattribs: Dict = None) -> 'Wipeout':
1120        """ Add a :class:`ezdxf.entities.Wipeout` entity, the masking area is
1121        defined by WCS `vertices`.
1122
1123        This method creates only a 2D entity in the xy-plane of the layout,
1124        the z-axis of the input vertices are ignored.
1125
1126        """
1127        wipeout = cast('Wipeout',
1128                       self.new_entity('WIPEOUT', dxfattribs=dxfattribs))
1129        wipeout.set_masking_area(vertices)
1130        doc = self.doc
1131        if doc and ('ACAD_WIPEOUT_VARS' not in doc.rootdict):
1132            doc.set_wipeout_variables(frame=0)
1133        return wipeout
1134
1135    def add_underlay(self, underlay_def: 'UnderlayDefinition',
1136                     insert: 'Vertex' = (0, 0, 0),
1137                     scale=(1, 1, 1), rotation: float = 0.,
1138                     dxfattribs: Dict = None) -> 'Underlay':
1139        """
1140        Add an :class:`~ezdxf.entities.Underlay` entity, requires a
1141        :class:`~ezdxf.entities.UnderlayDefinition` entity,
1142        see :ref:`tut_underlay`.  (requires DXF R2000)
1143
1144        Args:
1145            underlay_def: required underlay definition as :class:`~ezdxf.entities.UnderlayDefinition`
1146            insert: insertion point as 3D point in :ref:`WCS`
1147            scale:  underlay scaling factor as ``(x, y, z)`` tuple or as single
1148                value for uniform scaling for x, y and z
1149            rotation: rotation angle around the extrusion axis, default is the
1150                z-axis, in degrees
1151            dxfattribs: additional DXF attributes
1152
1153        """
1154        if self.dxfversion < DXF2000:
1155            raise DXFVersionError('UNDERLAY requires DXF R2000')
1156        dxfattribs = dict(dxfattribs or {})
1157        dxfattribs['insert'] = Vec3(insert)
1158        dxfattribs['underlay_def_handle'] = underlay_def.dxf.handle
1159        dxfattribs['rotation'] = float(rotation)
1160
1161        underlay = cast('Underlay', self.new_entity(
1162            underlay_def.entity_name, dxfattribs
1163        ))
1164        underlay.scaling = scale
1165        underlay.set_underlay_def(underlay_def)
1166        return underlay
1167
1168    def _save_dimstyle(self, name: str) -> str:
1169        if name in self.doc.dimstyles:
1170            return name
1171        logger.debug(
1172            f'Replacing undefined DIMSTYLE "{name}" by "Standard" DIMSTYLE.')
1173        return 'Standard'
1174
1175    def add_linear_dim(self,
1176                       base: 'Vertex',
1177                       p1: 'Vertex',
1178                       p2: 'Vertex',
1179                       location: 'Vertex' = None,
1180                       text: str = "<>",
1181                       angle: float = 0,
1182                       # 0=horizontal, 90=vertical, else=rotated
1183                       text_rotation: float = None,
1184                       dimstyle: str = 'EZDXF',
1185                       override: Dict = None,
1186                       dxfattribs: Dict = None) -> 'DimStyleOverride':
1187        """
1188        Add horizontal, vertical and rotated :class:`~ezdxf.entities.Dimension`
1189        line. If an :class:`~ezdxf.math.UCS` is used for dimension line rendering,
1190        all point definitions in UCS coordinates, translation into :ref:`WCS`
1191        and :ref:`OCS` is done by the rendering function. Extrusion vector is
1192        defined by UCS or ``(0, 0, 1)`` by default.
1193        See also: :ref:`tut_linear_dimension`
1194
1195        This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
1196        to create the necessary dimension geometry, you have to call
1197        :meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two step
1198        process allows additional processing steps on the
1199        :class:`~ezdxf.entities.Dimension` entity between creation and rendering.
1200
1201        .. note::
1202
1203            `ezdxf` ignores some DIMSTYLE variables, so render results may differ
1204            from BricsCAD or AutoCAD.
1205
1206        Args:
1207            base: location of dimension line, any point on the dimension line or
1208                its extension will do (in UCS)
1209            p1: measurement point 1 and start point of extension line 1 (in UCS)
1210            p2: measurement point 2 and start point of extension line 2 (in UCS)
1211            location: user defined location for text mid point (in UCS)
1212            text: ``None`` or ``"<>"`` the measurement is drawn as text,
1213                ``" "`` (one space) suppresses the dimension text, everything
1214                else `text` is drawn as dimension text
1215            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1216                table entry), default is ``'EZDXF'``
1217            angle: angle from ucs/wcs x-axis to dimension line in degrees
1218            text_rotation: rotation angle of the dimension text as absolute
1219                angle (x-axis=0, y-axis=90) in degrees
1220            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1221            dxfattribs: additional DXF attributes for
1222                :class:`~ezdxf.entities.Dimension` entity
1223
1224        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1225
1226        """
1227        type_ = {'dimtype': const.DIM_LINEAR | const.DIM_BLOCK_EXCLUSIVE}
1228        dimline = cast('Dimension',
1229                       self.new_entity('DIMENSION', dxfattribs=type_))
1230        dxfattribs = dict(dxfattribs or {})
1231        dxfattribs['dimstyle'] = self._save_dimstyle(dimstyle)
1232        dxfattribs['defpoint'] = Vec3(base)  # group code 10
1233        dxfattribs['text'] = str(text)
1234        dxfattribs['defpoint2'] = Vec3(p1)  # group code 13
1235        dxfattribs['defpoint3'] = Vec3(p2)  # group code 14
1236        dxfattribs['angle'] = float(angle)
1237
1238        # text_rotation ALWAYS overrides implicit angles as absolute angle
1239        # (x-axis=0, y-axis=90)!
1240        if text_rotation is not None:
1241            dxfattribs['text_rotation'] = float(text_rotation)
1242        dimline.update_dxf_attribs(dxfattribs)
1243
1244        style = DimStyleOverride(dimline, override=override)
1245        if location is not None:
1246            # special version, just for linear dimension
1247            style.set_location(location, leader=False, relative=False)
1248        return style
1249
1250    def add_multi_point_linear_dim(self,
1251                                   base: 'Vertex',
1252                                   points: Iterable['Vertex'],
1253                                   angle: float = 0,
1254                                   ucs: 'UCS' = None,
1255                                   avoid_double_rendering: bool = True,
1256                                   dimstyle: str = 'EZDXF',
1257                                   override: Dict = None,
1258                                   dxfattribs: Dict = None,
1259                                   discard=False) -> None:
1260        """
1261        Add multiple linear dimensions for iterable `points`. If an
1262        :class:`~ezdxf.math.UCS` is used for dimension line rendering, all point
1263        definitions in UCS coordinates, translation into :ref:`WCS` and
1264        :ref:`OCS` is done by the rendering function. Extrusion vector is
1265        defined by UCS or ``(0, 0, 1)`` by default. See also:
1266        :ref:`tut_linear_dimension`
1267
1268        This method sets many design decisions by itself, the necessary geometry
1269        will be generated automatically, no required nor possible
1270        :meth:`~ezdxf.entities.DimStyleOverride.render` call.
1271        This method is easy to use but you get what you get.
1272
1273        .. note::
1274
1275            `ezdxf` ignores some DIMSTYLE variables, so render results may
1276            differ from BricsCAD or AutoCAD.
1277
1278        Args:
1279            base: location of dimension line, any point on the dimension line
1280                or its extension will do (in UCS)
1281            points: iterable of measurement points (in UCS)
1282            angle: angle from ucs/wcs x-axis to dimension line in degrees
1283                (``0`` = horizontal, ``90`` = vertical)
1284            ucs: user defined coordinate system
1285            avoid_double_rendering: suppresses the first extension line and the
1286                first arrow if possible for continued dimension entities
1287            dimstyle: dimension style name (DimStyle table entry),
1288                default is ``'EZDXF'``
1289            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1290            dxfattribs: additional DXF attributes for :class:`~ezdxf.entities.Dimension`
1291                entity
1292            discard: discard rendering result for friendly CAD applications like
1293                BricsCAD to get a native and likely better rendering result.
1294                (does not work with AutoCAD)
1295
1296        """
1297        multi_point_linear_dimension(
1298            cast('GenericLayoutType', self),
1299            base=base,
1300            points=points,
1301            angle=angle,
1302            ucs=ucs,
1303            avoid_double_rendering=avoid_double_rendering,
1304            dimstyle=dimstyle,
1305            override=override,
1306            dxfattribs=dxfattribs,
1307            discard=discard,
1308        )
1309
1310    def add_aligned_dim(self,
1311                        p1: 'Vertex',
1312                        p2: 'Vertex',
1313                        distance: float,
1314                        text: str = "<>",
1315                        dimstyle: str = 'EZDXF',
1316                        override: Dict = None,
1317                        dxfattribs: Dict = None) -> 'DimStyleOverride':
1318        """
1319        Add linear dimension aligned with measurement points `p1` and `p2`. If
1320        an :class:`~ezdxf.math.UCS` is used for dimension line rendering, all
1321        point definitions in UCS coordinates, translation into :ref:`WCS`
1322        and :ref:`OCS` is done by the rendering function. Extrusion vector
1323        is defined by UCS or ``(0, 0, 1)`` by default.
1324        See also: :ref:`tut_linear_dimension`
1325
1326        This method returns a :class:`~ezdxf.entities.DimStyleOverride` object,
1327        to create the necessary dimension geometry, you have to  call
1328        :meth:`DimStyleOverride.render` manually, this two step process allows
1329        additional processing steps on the  :class:`~ezdxf.entities.Dimension`
1330        entity between creation and rendering.
1331
1332        .. note::
1333
1334            `ezdxf` ignores some DIMSTYLE variables, so render results may
1335            differ from BricsCAD or AutoCAD.
1336
1337        Args:
1338            p1: measurement point 1 and start point of extension line 1 (in UCS)
1339            p2: measurement point 2 and start point of extension line 2 (in UCS)
1340            distance: distance of dimension line from measurement points
1341            text: None or "<>" the measurement is drawn as text, " " (one space)
1342                suppresses the dimension text, everything else `text` is drawn
1343                as dimension text
1344            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1345                table entry), default is ``'EZDXF'``
1346            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1347            dxfattribs: DXF attributes for :class:`~ezdxf.entities.Dimension`
1348                entity
1349
1350        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1351
1352        """
1353        p1 = Vec3(p1)
1354        p2 = Vec3(p2)
1355        direction = p2 - p1
1356        angle = direction.angle_deg
1357        base = direction.orthogonal().normalize(distance) + p1
1358        return self.add_linear_dim(
1359            base=base,
1360            p1=p1,
1361            p2=p2,
1362            dimstyle=dimstyle,
1363            text=text,
1364            angle=angle,
1365            override=override,
1366            dxfattribs=dxfattribs,
1367        )
1368
1369    def add_angular_dim(self,
1370                        base: 'Vertex',
1371                        line1: Tuple['Vertex', 'Vertex'],
1372                        line2: Tuple['Vertex', 'Vertex'],
1373                        location: 'Vertex' = None,
1374                        text: str = "<>",
1375                        text_rotation: float = None,
1376                        dimstyle: str = 'EZDXF',
1377                        override: Dict = None,
1378                        dxfattribs: Dict = None) -> 'DimStyleOverride':
1379        """
1380        Add angular :class:`~ezdxf.entities.Dimension` from 2 lines.
1381        If an :class:`~ezdxf.math.UCS` is used for angular dimension rendering,
1382        all point definitions in UCS coordinates, translation into :ref:`WCS`
1383        and :ref:`OCS` is done by the rendering function. Extrusion vector is
1384        defined by UCS or ``(0, 0, 1)`` by default.
1385
1386        This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
1387        to create the necessary dimension geometry, you have to call
1388        :meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two step
1389        process allows additional processing steps on the
1390        :class:`~ezdxf.entities.Dimension` entity between creation and rendering.
1391
1392        .. note::
1393
1394            `ezdxf` ignores some DIMSTYLE variables, so render results may
1395            differ from BricsCAD or AutoCAD.
1396
1397        Args:
1398            base: location of dimension line, any point on the dimension line or
1399                its extension will do (in UCS)
1400            line1: specifies start leg of the angle (start point, end point) and
1401                determines extension line 1 (in UCS)
1402            line2: specifies end leg of the angle (start point, end point) and
1403                determines extension line 2 (in UCS)
1404            location: user defined location for text mid point (in UCS)
1405            text: ``None`` or ``"<>"`` the measurement is drawn as text, ``" "``
1406                (one space) suppresses the dimension text, everything else
1407                `text` is drawn as dimension text
1408            text_rotation: rotation angle of the dimension text as absolute
1409                angle (x-axis=0, y-axis=90) in degrees
1410            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1411                table entry), default is ``'EZDXF'``
1412            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1413            dxfattribs: additional DXF attributes for
1414                :class:`~ezdxf.entities.Dimension` entity
1415
1416        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1417
1418        (not implemented yet!)
1419
1420        """
1421        type_ = {'dimtype': const.DIM_ANGULAR | const.DIM_BLOCK_EXCLUSIVE}
1422        dimline = cast('Dimension',
1423                       self.new_entity('DIMENSION', dxfattribs=type_).cast())
1424
1425        dxfattribs = dict(dxfattribs or {})
1426        dxfattribs['dimstyle'] = self._save_dimstyle(dimstyle)
1427        dxfattribs['text'] = str(text)
1428
1429        dxfattribs['defpoint2'] = Vec3(line1[0])  # group code 13
1430        dxfattribs['defpoint3'] = Vec3(line1[1])  # group code 14
1431        dxfattribs['defpoint4'] = Vec3(line2[0])  # group code 15
1432        dxfattribs['defpoint'] = Vec3(line2[1])  # group code 10
1433        dxfattribs['defpoint5'] = Vec3(base)  # group code 16
1434
1435        # text_rotation ALWAYS overrides implicit angles as absolute angle (x-axis=0, y-axis=90)!
1436        if text_rotation is not None:
1437            dxfattribs['text_rotation'] = float(text_rotation)
1438
1439        dimline.update_dxf_attribs(dxfattribs)
1440        style = DimStyleOverride(dimline, override=override)
1441        if location is not None:
1442            style.user_location_override(location)
1443        return style
1444
1445    def add_angular_3p_dim(self,
1446                           base: 'Vertex',
1447                           center: 'Vertex',
1448                           p1: 'Vertex',
1449                           p2: 'Vertex',
1450                           location: 'Vertex' = None,
1451                           text: str = "<>",
1452                           text_rotation: float = None,
1453                           dimstyle: str = 'EZDXF',
1454                           override: Dict = None,
1455                           dxfattribs: Dict = None) -> 'DimStyleOverride':
1456        """
1457        Add angular :class:`~ezdxf.entities.Dimension` from 3 points
1458        (center, p1, p2).
1459        If an :class:`~ezdxf.math.UCS` is used for angular dimension rendering,
1460        all point definitions in UCS coordinates, translation into :ref:`WCS`
1461        and :ref:`OCS` is done by the rendering function. Extrusion vector is
1462        defined by UCS or ``(0, 0, 1)`` by default.
1463
1464        This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
1465        to create the necessary dimension geometry, you have to call
1466        :meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two step
1467        process allows additional processing steps on the
1468        :class:`~ezdxf.entities.Dimension` entity between creation and rendering.
1469
1470        .. note::
1471
1472            `ezdxf` ignores some DIMSTYLE variables, so render results may
1473            differ from BricsCAD or AutoCAD.
1474
1475        Args:
1476            base: location of dimension line, any point on the dimension line
1477                or its extension will do (in UCS)
1478            center: specifies the vertex of the angle
1479            p1: specifies start leg of the angle (center -> p1) and end point
1480                of extension line 1 (in UCS)
1481            p2: specifies end leg of the  angle (center -> p2) and end point
1482                of extension line 2 (in UCS)
1483            location: user defined location for text mid point (in UCS)
1484            text: ``None`` or ``"<>"`` the measurement is drawn as text, ``" "``
1485                (one space) suppresses the dimension text, everything else
1486                `text` is drawn as dimension text
1487            text_rotation: rotation angle of the dimension text as absolute
1488                angle (x-axis=0, y-axis=90) in degrees
1489            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1490                table entry), default is ``'EZDXF'``
1491            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1492            dxfattribs: additional DXF attributes for
1493                :class:`~ezdxf.entities.Dimension` entity
1494
1495        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1496
1497        (not implemented yet!)
1498
1499        """
1500        type_ = {'dimtype': const.DIM_ANGULAR_3P | const.DIM_BLOCK_EXCLUSIVE}
1501        dimline = cast('Dimension',
1502                       self.new_entity('DIMENSION', dxfattribs=type_))
1503        dxfattribs = dict(dxfattribs or {})
1504        dxfattribs['dimstyle'] = self._save_dimstyle(dimstyle)
1505        dxfattribs['text'] = str(text)
1506        dxfattribs['defpoint'] = Vec3(base)
1507        dxfattribs['defpoint2'] = Vec3(p1)
1508        dxfattribs['defpoint3'] = Vec3(p2)
1509        dxfattribs['defpoint4'] = Vec3(center)
1510
1511        # text_rotation ALWAYS overrides implicit angles as absolute angle
1512        # (x-axis=0, y-axis=90)!
1513        if text_rotation is not None:
1514            dxfattribs['text_rotation'] = float(text_rotation)
1515
1516        dimline.update_dxf_attribs(dxfattribs)
1517        style = DimStyleOverride(dimline, override=override)
1518        if location is not None:
1519            style.user_location_override(location)
1520        return style
1521
1522    def add_diameter_dim(self,
1523                         center: 'Vertex',
1524                         mpoint: 'Vertex' = None,
1525                         radius: float = None,
1526                         angle: float = None,
1527                         location: 'Vertex' = None,
1528                         text: str = "<>",
1529                         dimstyle: str = 'EZ_RADIUS',
1530                         override: Dict = None,
1531                         dxfattribs: Dict = None) -> 'DimStyleOverride':
1532        """
1533        Add a diameter :class:`~ezdxf.entities.Dimension` line. The diameter
1534        dimension line requires a `center` point and a point `mpoint` on the
1535        circle or as an alternative a `radius` and a dimension line `angle` in
1536        degrees.
1537
1538        If an :class:`~ezdxf.math.UCS` is used for dimension line rendering,
1539        all point definitions in UCS coordinates, translation into :ref:`WCS`
1540        and :ref:`OCS` is done by the rendering function. Extrusion vector is
1541        defined by UCS or ``(0, 0, 1)`` by default.
1542
1543        This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
1544        to create the necessary dimension geometry, you have to call
1545        :meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two step
1546        process allows additional processing steps on the :class:`~ezdxf.entities.Dimension`
1547        entity between creation and rendering.
1548
1549        .. note::
1550
1551            `ezdxf` ignores some DIMSTYLE variables, so render results may
1552            differ from BricsCAD or AutoCAD.
1553
1554        Args:
1555            center: specifies the center of the circle (in UCS)
1556            mpoint: specifies the measurement point on the circle (in UCS)
1557            radius: specify radius, requires argument `angle`, overrides `p1` argument
1558            angle: specify angle of dimension line in degrees, requires argument
1559                `radius`, overrides `p1` argument
1560            location: user defined location for text mid point (in UCS)
1561            text: ``None`` or ``"<>"`` the measurement is drawn as text, ``" "``
1562                (one space) suppresses the dimension text, everything else
1563                `text` is drawn as dimension text
1564            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1565                table entry), default is ``'EZ_RADIUS'``
1566            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1567            dxfattribs: additional DXF attributes for
1568                :class:`~ezdxf.entities.Dimension` entity
1569
1570        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1571
1572        """
1573        type_ = {'dimtype': const.DIM_DIAMETER | const.DIM_BLOCK_EXCLUSIVE}
1574        dimline = cast('Dimension',
1575                       self.new_entity('DIMENSION', dxfattribs=type_))
1576        center = Vec3(center)
1577        if location is not None:
1578            if radius is None:
1579                raise ValueError("Argument radius is required.")
1580            location = Vec3(location)
1581
1582            # (center - location) just works as expected, but in my
1583            # understanding it should be: (location - center)
1584            radius_vec = (center - location).normalize(length=radius)
1585        else:  # defined by mpoint = measurement point on circle
1586            if mpoint is None:  # defined by radius and angle
1587                if angle is None:
1588                    raise ValueError("Argument angle or mpoint required.")
1589                if radius is None:
1590                    raise ValueError("Argument radius or mpoint required.")
1591                radius_vec = Vec3.from_deg_angle(angle, radius)
1592            else:
1593                radius_vec = Vec3(mpoint) - center
1594
1595        p1 = center + radius_vec
1596        p2 = center - radius_vec
1597        dxfattribs = dict(dxfattribs or {})
1598        dxfattribs['dimstyle'] = self._save_dimstyle(dimstyle)
1599        dxfattribs['defpoint'] = Vec3(p1)  # group code 10
1600        dxfattribs['defpoint4'] = Vec3(p2)  # group code 15
1601        dxfattribs['text'] = str(text)
1602
1603        dimline.update_dxf_attribs(dxfattribs)
1604
1605        style = DimStyleOverride(dimline, override=override)
1606        if location is not None:
1607            style.user_location_override(location)
1608        return style
1609
1610    def add_diameter_dim_2p(self,
1611                            p1: 'Vertex',
1612                            p2: 'Vertex',
1613                            text: str = "<>",
1614                            dimstyle: str = 'EZ_RADIUS',
1615                            override: Dict = None,
1616                            dxfattribs: Dict = None) -> 'DimStyleOverride':
1617        """
1618        Shortcut method to create a diameter dimension by two points on the
1619        circle and the measurement text at the default location defined by the
1620        associated `dimstyle`, for further information see general method
1621        :func:`add_diameter_dim`. Center point of the virtual circle is the
1622        mid point between `p1` and `p2`.
1623
1624        - dimstyle ``'EZ_RADIUS'``: places the dimension text outside
1625        - dimstyle ``'EZ_RADIUS_INSIDE'``: places the dimension text inside
1626
1627        Args:
1628            p1: first point of the circle (in UCS)
1629            p2: second point on the opposite side of the center point of the
1630                circle (in UCS)
1631            text: ``None`` or ``"<>"`` the measurement is drawn as text, ``" "``
1632                (one space) suppresses the dimension text, everything else
1633                `text` is drawn as dimension text
1634            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1635                table entry), default is ``'EZ_RADIUS'``
1636            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1637            dxfattribs: additional DXF attributes for
1638                :class:`~ezdxf.entities.Dimension` entity
1639
1640        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1641
1642        """
1643        mpoint = Vec3(p1)
1644        center = mpoint.lerp(p2)
1645        return self.add_diameter_dim(center, mpoint, text=text,
1646                                     dimstyle=dimstyle,
1647                                     override=override, dxfattribs=dxfattribs)
1648
1649    def add_radius_dim(self,
1650                       center: 'Vertex',
1651                       mpoint: 'Vertex' = None,
1652                       radius: float = None,
1653                       angle: float = None,
1654                       location: 'Vertex' = None,
1655                       text: str = "<>",
1656                       dimstyle: str = 'EZ_RADIUS',
1657                       override: Dict = None,
1658                       dxfattribs: Dict = None) -> 'DimStyleOverride':
1659        """
1660        Add a radius :class:`~ezdxf.entities.Dimension` line. The radius
1661        dimension line requires a `center` point and a point `mpoint` on the
1662        circle or as an alternative a `radius` and a dimension line `angle` in
1663        degrees. See also: :ref:`tut_radius_dimension`
1664
1665        If an :class:`~ezdxf.math.UCS` is used for dimension line rendering,
1666        all point definitions in UCS coordinates, translation into :ref:`WCS`
1667        and :ref:`OCS` is done by the rendering function. Extrusion vector is
1668        defined by UCS or ``(0, 0, 1)`` by default.
1669
1670        This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
1671        to create the necessary dimension geometry, you have to call
1672        :meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two step
1673        process allows additional processing steps on the
1674        :class:`~ezdxf.entities.Dimension` entity between creation and rendering.
1675
1676        Following render types are supported:
1677
1678        - Default text location outside: text aligned with dimension line;
1679          dimension style: ``'EZ_RADIUS'``
1680        - Default text location outside horizontal: ``'EZ_RADIUS'`` + dimtoh=1
1681        - Default text location inside: text aligned with dimension line;
1682          dimension style: ``'EZ_RADIUS_INSIDE'``
1683        - Default text location inside horizontal:``'EZ_RADIUS_INSIDE'`` + dimtih=1
1684        - User defined text location: argument `location` != ``None``, text
1685          aligned with dimension line; dimension style: ``'EZ_RADIUS'``
1686        - User defined text location horizontal: argument `location` != ``None``,
1687          ``'EZ_RADIUS'`` + dimtoh=1 for text outside horizontal, ``'EZ_RADIUS'``
1688          + dimtih=1 for text inside horizontal
1689
1690        Placing the dimension text at a user defined `location`, overrides the
1691        `mpoint` and the `angle` argument, but requires a given `radius`
1692        argument. The `location` argument does not define the exact text
1693        location, instead it defines the dimension line starting at `center`
1694        and the measurement text midpoint projected on this dimension line
1695        going through `location`, if text is aligned to the dimension line.
1696        If text is horizontal, `location` is the kink point of the dimension
1697        line from radial to horizontal direction.
1698
1699        .. note::
1700
1701            `ezdxf` ignores some DIMSTYLE variables, so render results may
1702            differ from BricsCAD or AutoCAD.
1703
1704        Args:
1705            center: center point of the circle (in UCS)
1706            mpoint: measurement point on the circle, overrides `angle` and
1707                `radius` (in UCS)
1708            radius: radius in drawing units, requires argument `angle`
1709            angle: specify angle of dimension line in degrees, requires
1710                argument `radius`
1711            location: user defined dimension text location, overrides `mpoint`
1712                and `angle`, but requires `radius` (in UCS)
1713            text: ``None`` or ``"<>"`` the measurement is drawn as text, ``" "``
1714                (one space) suppresses the dimension text, everything else
1715                `text` is drawn as dimension text
1716            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1717                table entry), default is ``'EZ_RADIUS'``
1718            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1719            dxfattribs: additional DXF attributes for
1720                :class:`~ezdxf.entities.Dimension` entity
1721
1722        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1723
1724        """
1725        type_ = {'dimtype': const.DIM_RADIUS | const.DIM_BLOCK_EXCLUSIVE}
1726        dimline = cast('Dimension',
1727                       self.new_entity('DIMENSION', dxfattribs=type_))
1728        center = Vec3(center)
1729        if location is not None:
1730            if radius is None:
1731                raise ValueError("Argument radius is required.")
1732            location = Vec3(location)
1733            radius_vec = (location - center).normalize(length=radius)
1734            mpoint = center + radius_vec
1735        else:  # defined by mpoint = measurement point on circle
1736            if mpoint is None:  # defined by radius and angle
1737                if angle is None:
1738                    raise ValueError("Argument angle or mpoint required.")
1739                if radius is None:
1740                    raise ValueError("Argument radius or mpoint required.")
1741                mpoint = center + Vec3.from_deg_angle(angle, radius)
1742
1743        dxfattribs = dict(dxfattribs or {})
1744        dxfattribs['dimstyle'] = self._save_dimstyle(dimstyle)
1745        dxfattribs['defpoint4'] = Vec3(mpoint)  # group code 15
1746        dxfattribs['defpoint'] = Vec3(center)  # group code 10
1747        dxfattribs['text'] = str(text)
1748
1749        dimline.update_dxf_attribs(dxfattribs)
1750
1751        style = DimStyleOverride(dimline, override=override)
1752        if location is not None:
1753            style.user_location_override(location)
1754
1755        return style
1756
1757    def add_radius_dim_2p(self,
1758                          center: 'Vertex',
1759                          mpoint: 'Vertex',
1760                          text: str = "<>",
1761                          dimstyle: str = 'EZ_RADIUS',
1762                          override: Dict = None,
1763                          dxfattribs: Dict = None) -> 'DimStyleOverride':
1764        """
1765        Shortcut method to create a radius dimension by center point,
1766        measurement point on the circle and the measurement text at the default
1767        location defined by the associated `dimstyle`, for further information
1768        see general method :func:`add_radius_dim`.
1769
1770        - dimstyle ``'EZ_RADIUS'``: places the dimension text outside
1771        - dimstyle ``'EZ_RADIUS_INSIDE'``: places the dimension text inside
1772
1773        Args:
1774            center: center point of the circle (in UCS)
1775            mpoint: measurement point on the circle (in UCS)
1776            text: ``None`` or ``"<>"`` the measurement is drawn as text, ``" "``
1777                (one space) suppresses the dimension text, everything else
1778                `text` is drawn as dimension text
1779            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1780                table entry), default is ``'EZ_RADIUS'``
1781            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1782            dxfattribs: additional DXF attributes for
1783                :class:`~ezdxf.entities.Dimension` entity
1784
1785        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1786
1787        """
1788        return self.add_radius_dim(center, mpoint, text=text, dimstyle=dimstyle,
1789                                   override=override, dxfattribs=dxfattribs)
1790
1791    def add_radius_dim_cra(self,
1792                           center: 'Vertex',
1793                           radius: float,
1794                           angle: float,
1795                           text: str = "<>",
1796                           dimstyle: str = 'EZ_RADIUS',
1797                           override: Dict = None,
1798                           dxfattribs: Dict = None) -> 'DimStyleOverride':
1799        """
1800        Shortcut method to create a radius dimension by (c)enter point,
1801        (r)adius and (a)ngle, the measurement text is placed at the default
1802        location defined by the associated `dimstyle`, for further information
1803        see general method :func:`add_radius_dim`.
1804
1805        - dimstyle ``'EZ_RADIUS'``: places the dimension text outside
1806        - dimstyle ``'EZ_RADIUS_INSIDE'``: places the dimension text inside
1807
1808        Args:
1809            center: center point of the circle (in UCS)
1810            radius: radius in drawing units
1811            angle: angle of dimension line in degrees
1812            text: ``None`` or ``"<>"`` the measurement is drawn as text, ``" "``
1813                (one space) suppresses the dimension text, everything else
1814                `text` is drawn as dimension text
1815            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1816                table entry), default is ``'EZ_RADIUS'``
1817            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1818            dxfattribs: additional DXF attributes for
1819                :class:`~ezdxf.entities.Dimension` entity
1820
1821        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1822
1823        """
1824        return self.add_radius_dim(center, radius=radius, angle=angle,
1825                                   text=text, dimstyle=dimstyle,
1826                                   override=override, dxfattribs=dxfattribs)
1827
1828    def add_ordinate_dim(self,
1829                         origin: 'Vertex',
1830                         feature_location: 'Vertex',
1831                         leader_endpoint: 'Vertex',
1832                         location: 'Vertex' = None,
1833                         text: str = "<>",
1834                         dimstyle: str = 'EZDXF',
1835                         override: Dict = None,
1836                         dxfattribs: Dict = None) -> DimStyleOverride:
1837        """
1838        Add ordinate :class:`~ezdxf.entities.Dimension` line.
1839        If an :class:`~ezdxf.math.UCS` is used for dimension line rendering,
1840        all point definitions in UCS coordinates, translation into :ref:`WCS`
1841        and :ref:`OCS` is done by the rendering function. Extrusion vector is
1842        defined by UCS or ``(0, 0, 1)`` by default.
1843
1844        This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
1845        to create the necessary dimension geometry, you have to call
1846        :meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two step
1847        process allows additional processing steps on the
1848        :class:`~ezdxf.entities.Dimension` entity between creation and rendering.
1849
1850        .. note::
1851
1852            `ezdxf` ignores some DIMSTYLE variables, so render results may
1853            differ from BricsCAD or AutoCAD.
1854
1855        Args:
1856            origin: specifies the origin of the ordinate coordinate system
1857                (in UCS)
1858            feature_location: feature location in UCS
1859            leader_endpoint: leader endpoint in UCS
1860            location: user defined location for text mid point (in UCS)
1861            text: ``None`` or ``"<>"`` the measurement is drawn as text, ``" "``
1862                (one space) suppresses the dimension text, everything else
1863                `text` is drawn as dimension text
1864            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1865                table entry), default is ``'EZDXF'``
1866            override: :class:`~ezdxf.entities.DimStyleOverride` attributes
1867            dxfattribs: additional DXF attributes for
1868                :class:`~ezdxf.entities.Dimension` entity
1869
1870        Returns: :class:`~ezdxf.entities.DimStyleOverride`
1871
1872        (not implemented yet!)
1873
1874        """
1875        type_ = {'dimtype': const.DIM_ORDINATE | const.DIM_BLOCK_EXCLUSIVE}
1876        dimline = cast('Dimension',
1877                       self.new_entity('DIMENSION', dxfattribs=type_))
1878        dxfattribs = dict(dxfattribs or {})
1879        dxfattribs['dimstyle'] = self._save_dimstyle(dimstyle)
1880        dxfattribs['defpoint'] = Vec3(origin)  # group code 10
1881        dxfattribs['defpoint2'] = Vec3(feature_location)  # group code 13
1882        dxfattribs['defpoint3'] = Vec3(leader_endpoint)  # group code 14
1883        dxfattribs['text'] = str(text)
1884        dimline.update_dxf_attribs(dxfattribs)
1885
1886        style = DimStyleOverride(dimline, override=override)
1887        if location is not None:
1888            style.user_location_override(location)
1889        return style
1890
1891    def add_arrow(self, name: str, insert: 'Vertex', size: float = 1.,
1892                  rotation: float = 0,
1893                  dxfattribs: Dict = None) -> Vec3:
1894        return ARROWS.render_arrow(self, name=name, insert=insert, size=size,
1895                                   rotation=rotation, dxfattribs=dxfattribs)
1896
1897    def add_arrow_blockref(self, name: str, insert: 'Vertex', size: float = 1.,
1898                           rotation: float = 0,
1899                           dxfattribs: Dict = None) -> Vec3:
1900        return ARROWS.insert_arrow(self, name=name, insert=insert, size=size,
1901                                   rotation=rotation, dxfattribs=dxfattribs)
1902
1903    def add_leader(self,
1904                   vertices: Iterable['Vertex'],
1905                   dimstyle: str = 'EZDXF',
1906                   override: Dict = None,
1907                   dxfattribs: Dict = None) -> 'Leader':
1908        """
1909        The :class:`~ezdxf.entities.Leader` entity represents an arrow, made up
1910        of one or more vertices (or spline fit points) and an arrowhead.
1911        The label or other content to which the :class:`~ezdxf.entities.Leader`
1912        is attached is stored as a separate entity, and is not part of the
1913        :class:`~ezdxf.entities.Leader` itself.
1914        (requires DXF R2000)
1915
1916        :class:`~ezdxf.entities.Leader` shares its styling infrastructure with
1917        :class:`~ezdxf.entities.Dimension`.
1918
1919        By default a :class:`~ezdxf.entities.Leader` without any annotation is
1920        created. For creating more fancy leaders and annotations see
1921        documentation provided by Autodesk or `Demystifying DXF: LEADER and MULTILEADER
1922        implementation notes <https://atlight.github.io/formats/dxf-leader.html>`_  .
1923
1924
1925        Args:
1926            vertices: leader vertices (in :ref:`WCS`)
1927            dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
1928                table entry), default is ``'EZDXF'``
1929            override: override :class:`~ezdxf.entities.DimStyleOverride` attributes
1930            dxfattribs: additional DXF attributes for
1931                :class:`~ezdxf.entities.Leader` entity
1932
1933        """
1934
1935        def filter_unsupported_dimstyle_attributes(attribs: Dict) -> Dict:
1936            return {k: v for k, v in attribs.items() if
1937                    k not in LEADER_UNSUPPORTED_DIMSTYLE_ATTRIBS}
1938
1939        if self.dxfversion < DXF2000:
1940            raise DXFVersionError('LEADER requires DXF R2000')
1941
1942        dxfattribs = dxfattribs or {}
1943        dxfattribs['dimstyle'] = self._save_dimstyle(dimstyle)
1944        dxfattribs.setdefault('annotation_type', 3)
1945        leader = cast('Leader', self.new_entity('LEADER', dxfattribs))
1946        leader.set_vertices(vertices)
1947        if override:
1948            override = filter_unsupported_dimstyle_attributes(override)
1949            if 'dimldrblk' in override:
1950                self.doc.acquire_arrow(override['dimldrblk'])
1951            # Class Leader() supports the required OverrideMixin() interface
1952            DimStyleOverride(cast('Dimension', leader),
1953                             override=override).commit()
1954        return leader
1955
1956    def add_mline(self, vertices: Iterable['Vertex'] = None,
1957                  *,
1958                  close: bool = False,
1959                  dxfattribs: Dict = None) -> 'MLine':
1960        """ Add a :class:`~ezdxf.entities.MLine` entity
1961
1962        Args:
1963            vertices: MLINE vertices (in :ref:`WCS`)
1964            close: `True` to add a closed MLINE
1965            dxfattribs: additional DXF attributes for
1966                :class:`~ezdxf.entities.MLine` entity
1967
1968        """
1969        if self.dxfversion < DXF2000:
1970            raise DXFVersionError('MLine requires DXF R2000')
1971        dxfattribs = dxfattribs or {}
1972        style_name = dxfattribs.pop('style_name', 'Standard')
1973        mline = cast('MLine', self.new_entity('MLINE', dxfattribs))
1974        # close() method regenerates geometry!
1975        mline.set_flag_state(mline.CLOSED, close)
1976        mline.set_style(style_name)
1977        if vertices:
1978            mline.extend(vertices)
1979        return mline
1980
1981
1982LEADER_UNSUPPORTED_DIMSTYLE_ATTRIBS = {'dimblk', 'dimblk1', 'dimblk2'}
1983