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