1# Purpose: dimension lines as composite entities build with basic dxf entities, but not the DIMENSION entity. 2# Created: 10.03.2010, 2018 adapted for ezdxf 3# Copyright (c) 2010-2018, Manfred Moitzi 4# License: MIT License 5""" 6Dimension lines as composite entities build with basic dxf entities, but not the DIMENSION entity. 7 8OBJECTS 9 10- LinearDimension 11- AngularDimension 12- ArcDimension 13- RadiusDimension 14 15PUBLIC MEMBERS 16 17dimstyles 18 dimstyle container 19 20 - new(name, kwargs) to create a new dimstyle 21 - get(name) to get a dimstyle, 'Default' if name does not exist 22 - setup(drawing) create Blocks and Layers in drawing 23""" 24from typing import Any, Dict, TYPE_CHECKING, Iterable, List, Tuple 25from math import radians, degrees, pi 26from abc import abstractmethod 27 28from ezdxf.math import Vec3, distance, lerp, ConstructionRay 29 30if TYPE_CHECKING: 31 from ezdxf.eztypes import Drawing, GenericLayoutType, Vertex 32 33DIMENSIONS_MIN_DISTANCE = 0.05 34DIMENSIONS_FLOATINGPOINT = '.' 35 36ANGLE_DEG = 180. / pi 37ANGLE_GRAD = 200. / pi 38ANGLE_RAD = 1. 39 40 41class DimStyle(dict): 42 """ 43 DimStyle parameter struct, a dumb object just to store values 44 45 """ 46 default_values = [ 47 # tick block name, use setup to generate default blocks <dimblk> <dimblk1> <dimblk2> 48 ('tick', 'DIMTICK_ARCH'), 49 # scale factor for ticks-block <dimtsz> <dimasz> 50 ('tickfactor', 1.), 51 # tick2x means tick is drawn only for one side, insert tick a second 52 # time rotated about 180 degree, but only one time at the dimension line 53 # ends, this is useful for arrow-like ticks. hint: set dimlineext to 0. <none> 54 ('tick2x', False), 55 # dimension value scale factor, value = drawing-units * scale <dimlfac> 56 ('scale', 100.), 57 # round dimension value to roundval fractional digits <dimdec> 58 ('roundval', 0), 59 # round dimension value to half units, round 0.4, 0.6 to 0.5 <dimrnd> 60 ('roundhalf', False), 61 # dimension value text color <dimclrt> 62 ('textcolor', 7), 63 # dimension value text height <dimtxt> 64 ('height', .5), 65 # dimension text prefix and suffix like 'x=' ... ' cm' <dimpost> 66 ('prefix', ''), 67 ('suffix', ''), 68 # dimension value text style <dimtxsty> 69 ('style', 'OpenSansCondensed-Light'), 70 # default layer for whole dimension object 71 ('layer', 'DIMENSIONS'), 72 # dimension line color index (0 from layer) <dimclrd> 73 ('dimlinecolor', 7), 74 # dimension line extensions (in dimline direction, left and right) <dimdle> 75 ('dimlineext', .3), 76 # draw dimension value text `textabove` drawing-units above the 77 # dimension line <dimgap> 78 ('textabove', 0.2), 79 # switch extension line False=off, True=on <dimse1> <dimse2> 80 ('dimextline', True), 81 # dimension extension line color index (0 from layer) <dimclre> 82 ('dimextlinecolor', 5), 83 # gap between measure target point and end of extension line <dimexo> 84 ('dimextlinegap', 0.3) 85 ] 86 87 def __init__(self, name: str, **kwargs): 88 super().__init__(DimStyle.default_values) 89 # dimstyle name 90 self['name'] = name 91 self.update(kwargs) 92 93 def __getattr__(self, attr: str) -> Any: 94 return self[attr] 95 96 def __setattr__(self, attr: str, value: Any) -> None: 97 self[attr] = value 98 99 100class DimStyles: 101 """ 102 DimStyle container 103 104 """ 105 106 def __init__(self): 107 self._styles = {} # type: Dict[str, DimStyle] 108 self.default = DimStyle('Default') 109 110 self.new( 111 "angle.deg", 112 scale=ANGLE_DEG, 113 suffix=str('°'), 114 roundval=0, 115 tick="DIMTICK_RADIUS", 116 tick2x=True, 117 dimlineext=0., 118 dimextline=False 119 ) 120 self.new( 121 "angle.grad", 122 scale=ANGLE_GRAD, 123 suffix='gon', 124 roundval=0, 125 tick="DIMTICK_RADIUS", 126 tick2x=True, 127 dimlineext=0., 128 dimextline=False 129 ) 130 self.new( 131 "angle.rad", 132 scale=ANGLE_RAD, 133 suffix='rad', 134 roundval=3, 135 tick="DIMTICK_RADIUS", 136 tick2x=True, 137 dimlineext=0., 138 dimextline=False 139 ) 140 141 def get(self, name: str) -> DimStyle: 142 """ 143 Get DimStyle() object by name. 144 """ 145 return self._styles.get(name, self.default) 146 147 def new(self, name: str, **kwargs) -> DimStyle: 148 """ 149 Create a new dimstyle 150 """ 151 style = DimStyle(name, **kwargs) 152 self._styles[name] = style 153 return style 154 155 @staticmethod 156 def setup(drawing: 'Drawing'): 157 """ 158 Insert necessary definitions into drawing: 159 160 ticks: DIMTICK_ARCH, DIMTICK_DOT, DIMTICK_ARROW 161 """ 162 # default pen assignment: 163 # 1 : 1.40mm - red 164 # 2 : 0.35mm - yellow 165 # 3 : 0.70mm - green 166 # 4 : 0.50mm - cyan 167 # 5 : 0.13mm - blue 168 # 6 : 1.00mm - magenta 169 # 7 : 0.25mm - white/black 170 # 8, 9 : 2.00mm 171 # >=10 : 1.40mm 172 173 dimcolor = {'color': dimstyles.default.dimextlinecolor, 'layer': 'BYBLOCK'} 174 color4 = {'color': 4, 'layer': 'BYBLOCK'} 175 color7 = {'color': 7, 'layer': 'BYBLOCK'} 176 177 block = drawing.blocks.new('DIMTICK_ARCH') 178 block.add_line(start=(0., +.5), end=(0., -.5), dxfattribs=dimcolor) 179 block.add_line(start=(-.2, -.2), end=(.2, +.2), dxfattribs=color4) 180 181 block = drawing.blocks.new('DIMTICK_DOT') 182 block.add_line(start=(0., .5), end=(0., -.5), dxfattribs=dimcolor) 183 block.add_circle(center=(0, 0), radius=.1, dxfattribs=color4) 184 185 block = drawing.blocks.new('DIMTICK_ARROW') 186 187 block.add_line(start=(0., .5), end=(0., -.50), dxfattribs=dimcolor) 188 block.add_solid([(0, 0), (.3, .05), (.3, -.05)], dxfattribs=color7) 189 190 block = drawing.blocks.new('DIMTICK_RADIUS') 191 block.add_solid([(0, 0), (.3, .05), (0.25, 0.), (.3, -.05)], dxfattribs=color7) 192 193 194dimstyles = DimStyles() # use this factory to create new dimstyles 195 196 197class _DimensionBase: 198 """ 199 Abstract base class for dimension lines. 200 201 """ 202 203 def __init__(self, dimstyle: str, layer: str, roundval: int): 204 self.dimstyle = dimstyles.get(dimstyle) 205 self.layer = layer 206 self.roundval = roundval 207 208 def prop(self, property_name: str) -> Any: 209 """ 210 Get dimension line properties by `property_name` with the possibility to override several properties. 211 """ 212 if property_name == 'layer': 213 return self.layer if self.layer is not None else self.dimstyle.layer 214 elif property_name == 'roundval': 215 return self.roundval if self.roundval is not None else self.dimstyle.roundval 216 else: # pass through self.dimstyle object DimStyle() 217 return self.dimstyle[property_name] 218 219 def format_dimtext(self, dimvalue: float) -> str: 220 """ 221 Format the dimension text. 222 """ 223 dimtextfmt = "%." + str(self.prop('roundval')) + "f" 224 dimtext = dimtextfmt % dimvalue 225 if DIMENSIONS_FLOATINGPOINT in dimtext: 226 # remove successional zeros 227 dimtext.rstrip('0') 228 # remove floating point as last char 229 dimtext.rstrip(DIMENSIONS_FLOATINGPOINT) 230 return self.prop('prefix') + dimtext + self.prop('suffix') 231 232 @abstractmethod 233 def render(self, layout: 'GenericLayoutType'): 234 pass 235 236 237class LinearDimension(_DimensionBase): 238 """ 239 Simple straight dimension line with two or more measure points, build with basic DXF entities. This is NOT a dxf 240 dimension entity. And This is a 2D element, so all z-values will be ignored! 241 242 """ 243 244 def __init__(self, pos: 'Vertex', measure_points: Iterable['Vertex'], angle: float = 0., dimstyle: str = 'Default', 245 layer: str = None, roundval: int = None): 246 """ 247 LinearDimension Constructor. 248 249 Args: 250 pos: location as (x, y) tuple of dimension line, line goes through this point 251 measure_points: list of points as (x, y) tuples to dimension (two or more) 252 angle: angle (in degree) of dimension line 253 dimstyle: dimstyle name, 'Default' - style is the default value 254 layer: dimension line layer, override the default value of dimstyle 255 roundval: count of decimal places 256 257 """ 258 super().__init__(dimstyle, layer, roundval) 259 self.angle = angle 260 self.measure_points = list(measure_points) 261 self.text_override = [""] * self.section_count 262 self.dimlinepos = Vec3(pos) 263 self.layout = None 264 265 def set_text(self, section: int, text: str) -> None: 266 """ 267 Set and override the text of the dimension text for the given dimension line section. 268 """ 269 self.text_override[section] = text 270 271 def _setup(self) -> None: 272 """ 273 Calc setup values and determines the point order of the dimension line points. 274 """ 275 self.measure_points = [Vec3(point) for point in self.measure_points] # type: List[Vec3] 276 dimlineray = ConstructionRay(self.dimlinepos, angle=radians(self.angle)) # Type: ConstructionRay 277 self.dimline_points = [self._get_point_on_dimline(point, dimlineray) for point in 278 self.measure_points] # type: List[Vec3] 279 self.point_order = self._indices_of_sorted_points(self.dimline_points) # type: List[int] 280 self._build_vectors() 281 282 def _get_dimline_point(self, index: int) -> 'Vertex': 283 """ 284 Get point on the dimension line, index runs left to right. 285 """ 286 return self.dimline_points[self.point_order[index]] 287 288 def _get_section_points(self, section: int) -> Tuple[Vec3, Vec3]: 289 """ 290 Get start and end point on the dimension line of dimension section. 291 """ 292 return self._get_dimline_point(section), self._get_dimline_point(section + 1) 293 294 def _get_dimline_bounds(self) -> Tuple[Vec3, Vec3]: 295 """ 296 Get the first and the last point of dimension line. 297 """ 298 return self._get_dimline_point(0), self._get_dimline_point(-1) 299 300 @property 301 def section_count(self) -> int: 302 """ count of dimline sections """ 303 return len(self.measure_points) - 1 304 305 @property 306 def point_count(self) -> int: 307 """ count of dimline points """ 308 return len(self.measure_points) 309 310 def render(self, layout: 'GenericLayoutType') -> None: 311 """ build dimension line object with basic dxf entities """ 312 self._setup() 313 self._draw_dimline(layout) 314 if self.prop('dimextline'): 315 self._draw_extension_lines(layout) 316 self._draw_text(layout) 317 self._draw_ticks(layout) 318 319 @staticmethod 320 def _indices_of_sorted_points(points: Iterable['Vertex']) -> List[int]: 321 """ get indices of points, for points sorted by x, y values """ 322 indexed_points = [(point, idx) for idx, point in enumerate(points)] 323 indexed_points.sort() 324 return [idx for point, idx in indexed_points] 325 326 def _build_vectors(self) -> None: 327 """ build unit vectors, parallel and normal to dimension line """ 328 point1, point2 = self._get_dimline_bounds() 329 self.parallel_vector = (Vec3(point2) - Vec3(point1)).normalize() 330 self.normal_vector = self.parallel_vector.orthogonal() 331 332 @staticmethod 333 def _get_point_on_dimline(point: 'Vertex', dimray: ConstructionRay) -> Vec3: 334 """ get the measure target point projection on the dimension line """ 335 return dimray.intersect(dimray.orthogonal(point)) 336 337 def _draw_dimline(self, layout: 'GenericLayoutType') -> None: 338 """ build dimension line entity """ 339 start_point, end_point = self._get_dimline_bounds() 340 341 dimlineext = self.prop('dimlineext') 342 if dimlineext > 0: 343 start_point = start_point - (self.parallel_vector * dimlineext) 344 end_point = end_point + (self.parallel_vector * dimlineext) 345 346 attribs = { 347 'color': self.prop('dimlinecolor'), 348 'layer': self.prop('layer'), 349 } 350 layout.add_line( 351 start=start_point, 352 end=end_point, 353 dxfattribs=attribs, 354 ) 355 356 def _draw_extension_lines(self, layout: 'GenericLayoutType') -> None: 357 """ build the extension lines entities """ 358 dimextlinegap = self.prop('dimextlinegap') 359 attribs = { 360 'color': self.prop('dimlinecolor'), 361 'layer': self.prop('layer'), 362 } 363 364 for dimline_point, target_point in zip(self.dimline_points, self.measure_points): 365 if distance(dimline_point, target_point) > max(dimextlinegap, DIMENSIONS_MIN_DISTANCE): 366 direction_vector = (target_point - dimline_point).normalize() 367 target_point = target_point - (direction_vector * dimextlinegap) 368 layout.add_line( 369 start=dimline_point, 370 end=target_point, 371 dxfattribs=attribs, 372 ) 373 374 def _draw_text(self, layout: 'GenericLayoutType') -> None: 375 """ build the dimension value text entity """ 376 attribs = { 377 'height': self.prop('height'), 378 'color': self.prop('textcolor'), 379 'layer': self.prop('layer'), 380 'rotation': self.angle, 381 'style': self.prop('style'), 382 } 383 for section in range(self.section_count): 384 dimvalue_text = self._get_dimvalue_text(section) 385 insert_point = self._get_text_insert_point(section) 386 layout.add_text( 387 text=dimvalue_text, 388 dxfattribs=attribs, 389 ).set_pos(insert_point, align='MIDDLE_CENTER') 390 391 def _get_dimvalue_text(self, section: int) -> str: 392 """ get the dimension value as text, distance from point1 to point2 """ 393 override = self.text_override[section] 394 if len(override): 395 return override 396 point1, point2 = self._get_section_points(section) 397 398 dimvalue = distance(point1, point2) * self.prop('scale') 399 return self.format_dimtext(dimvalue) 400 401 def _get_text_insert_point(self, section: int) -> Vec3: 402 """ get the dimension value text insert point """ 403 point1, point2 = self._get_section_points(section) 404 dist = self.prop('height') / 2. + self.prop('textabove') 405 return lerp(point1, point2) + (self.normal_vector * dist) 406 407 def _draw_ticks(self, layout: 'GenericLayoutType') -> None: 408 """ insert the dimension line ticks, (markers on the dimension line) """ 409 attribs = { 410 'xscale': self.prop('tickfactor'), 411 'yscale': self.prop('tickfactor'), 412 'layer': self.prop('layer'), 413 } 414 415 def add_tick(index: int, rotate: bool = False) -> None: 416 """ build the insert-entity for the tick block """ 417 attribs['rotation'] = self.angle + (180. if rotate else 0.) 418 layout.add_blockref( 419 insert=self._get_dimline_point(index), 420 name=self.prop('tick'), 421 dxfattribs=attribs, 422 ) 423 424 if self.prop('tick2x'): 425 for index in range(0, self.point_count - 1): 426 add_tick(index, False) 427 for index in range(1, self.point_count): 428 add_tick(index, True) 429 else: 430 for index in range(self.point_count): 431 add_tick(index, False) 432 433 434class AngularDimension(_DimensionBase): 435 """ 436 Draw an angle dimensioning line at dimline pos from start to end, dimension text is the angle build of the three 437 points start-center-end. 438 439 """ 440 DEG = ANGLE_DEG 441 GRAD = ANGLE_GRAD 442 RAD = ANGLE_RAD 443 444 def __init__(self, pos: 'Vertex', center: 'Vertex', start: 'Vertex', end: 'Vertex', 445 dimstyle: str = 'angle.deg', layer: str = None, roundval: int = None): 446 """ 447 AngularDimension constructor. 448 449 Args: 450 pos: location as (x, y) tuple of dimension line, line goes through this point 451 center: center point as (x, y) tuple of angle 452 start: line from center to start is the first side of the angle 453 end: line from center to end is the second side of the angle 454 dimstyle: dimstyle name, 'Default' - style is the default value 455 layer: dimension line layer, override the default value of dimstyle 456 roundval: count of decimal places 457 458 """ 459 super().__init__(dimstyle, layer, roundval) 460 self.dimlinepos = Vec3(pos) 461 self.center = Vec3(center) 462 self.start = Vec3(start) 463 self.end = Vec3(end) 464 465 def _setup(self) -> None: 466 """ setup calculation values """ 467 self.pos_radius = distance(self.center, self.dimlinepos) # type: float 468 self.radius = distance(self.center, self.start) # type: float 469 self.start_vector = (self.start - self.center).normalize() # type: Vec3 470 self.end_vector = (self.end - self.center).normalize() # type: Vec3 471 self.start_angle = self.start_vector.angle # type: float 472 self.end_angle = self.end_vector.angle # type: float 473 474 def render(self, layout: 'GenericLayoutType') -> None: 475 """ build dimension line object with basic dxf entities """ 476 477 self._setup() 478 self._draw_dimension_line(layout) 479 if self.prop('dimextline'): 480 self._draw_extension_lines(layout) 481 self._draw_dimension_text(layout) 482 self._draw_ticks(layout) 483 484 def _draw_dimension_line(self, layout: 'GenericLayoutType') -> None: 485 """ draw the dimension line from start- to endangle. """ 486 layout.add_arc( 487 radius=self.pos_radius, 488 center=self.center, 489 start_angle=degrees(self.start_angle), 490 end_angle=degrees(self.end_angle), 491 dxfattribs={ 492 'layer': self.prop('layer'), 493 'color': self.prop('dimlinecolor'), 494 } 495 ) 496 497 def _draw_extension_lines(self, layout: 'GenericLayoutType') -> None: 498 """ build the extension lines entities """ 499 for vector in [self.start_vector, self.end_vector]: 500 layout.add_line( 501 start=self._get_extline_start(vector), 502 end=self._get_extline_end(vector), 503 dxfattribs={ 504 'layer': self.prop('layer'), 505 'color': self.prop('dimextlinecolor'), 506 } 507 ) 508 509 def _get_extline_start(self, vector: Vec3) -> Vec3: 510 return self.center + (vector * self.prop('dimextlinegap')) 511 512 def _get_extline_end(self, vector: Vec3) -> Vec3: 513 return self.center + (vector * self.pos_radius) 514 515 def _draw_dimension_text(self, layout: 'GenericLayoutType') -> None: 516 attribs = { 517 'height': self.prop('height'), 518 'rotation': degrees((self.start_angle + self.end_angle) / 2 - pi / 2.), 519 'layer': self.prop('layer'), 520 'style': self.prop('style'), 521 'color': self.prop('textcolor'), 522 } 523 layout.add_text( 524 text=self._get_dimtext(), 525 dxfattribs=attribs, 526 ).set_pos(self._get_text_insert_point(), align='MIDDLE_CENTER') 527 528 def _get_text_insert_point(self) -> Vec3: 529 midvector = ((self.start_vector + self.end_vector) / 2.).normalize() 530 length = self.pos_radius + self.prop('textabove') + self.prop('height') / 2. 531 return self.center + (midvector * length) 532 533 def _draw_ticks(self, layout: 'GenericLayoutType') -> None: 534 attribs = { 535 'xscale': self.prop('tickfactor'), 536 'yscale': self.prop('tickfactor'), 537 'layer': self.prop('layer'), 538 } 539 for vector, mirror in [(self.start_vector, False), (self.end_vector, self.prop('tick2x'))]: 540 insert_point = self.center + (vector * self.pos_radius) 541 rotation = vector.angle + pi / 2. 542 attribs['rotation'] = degrees(rotation + (pi if mirror else 0.)) 543 layout.add_blockref( 544 insert=insert_point, 545 name=self.prop('tick'), 546 dxfattribs=attribs, 547 ) 548 549 def _get_dimtext(self) -> str: 550 # set scale = ANGLE_DEG for degrees (circle = 360 deg) 551 # set scale = ANGLE_GRAD for grad(circle = 400 grad) 552 # set scale = ANGLE_RAD for rad(circle = 2*pi) 553 angle = (self.end_angle - self.start_angle) * self.prop('scale') 554 return self.format_dimtext(angle) 555 556 557class ArcDimension(AngularDimension): 558 """ 559 Arc is defined by start- and endpoint on arc and the center point, or by three points lying on the arc if acr3points 560 is True. Measured length goes from start- to endpoint. The dimension line goes through the dimlinepos. 561 562 """ 563 564 def __init__(self, pos: 'Vertex', center: 'Vertex', start: 'Vertex', end: 'Vertex', arc3points: bool = False, 565 dimstyle: str = 'Default', layer: str = None, roundval: int = None): 566 """ 567 Args: 568 pos: location as (x, y) tuple of dimension line, line goes through this point 569 center: center point of arc 570 start: start point of arc 571 end: end point of arc 572 arc3points: if **True** arc is defined by three points on the arc (center, start, end) 573 dimstyle: dimstyle name, 'Default' - style is the default value 574 layer: dimension line layer, override the default value of dimstyle 575 roundval: count of decimal places 576 577 """ 578 super().__init__(pos, center, start, end, dimstyle, layer, roundval) 579 self.arc3points = arc3points 580 581 def _setup(self) -> None: 582 super()._setup() 583 if self.arc3points: 584 self.center = center_of_3points_arc(self.center, self.start, self.end) 585 586 def _get_extline_start(self, vector: Vec3) -> Vec3: 587 return self.center + (vector * (self.radius + self.prop('dimextlinegap'))) 588 589 def _get_extline_end(self, vector: Vec3) -> Vec3: 590 return self.center + (vector * self.pos_radius) 591 592 def _get_dimtext(self) -> str: 593 arc_length = (self.end_angle - self.start_angle) * self.radius * self.prop('scale') 594 return self.format_dimtext(arc_length) 595 596 597class RadialDimension(_DimensionBase): 598 """ 599 Draw a radius dimension line from `target` in direction of `center` with length drawing units. RadiusDimension has 600 a special tick!! 601 """ 602 603 def __init__(self, center: 'Vertex', target: 'Vertex', length: float = 1., dimstyle: str = 'Default', 604 layer: str = None, roundval: int = None): 605 """ 606 Args: 607 center: center point of radius 608 target: target point of radius 609 length: length of radius arrow (drawing length) 610 dimstyle: dimstyle name, 'Default' - style is the default value 611 layer: dimension line layer, override the default value of dimstyle 612 roundval: count of decimal places 613 614 """ 615 super().__init__(dimstyle, layer, roundval) 616 self.center = Vec3(center) 617 self.target = Vec3(target) 618 self.length = float(length) 619 620 def _setup(self) -> None: 621 self.target_vector = (self.target - self.center).normalize() # type: Vec3 622 self.radius = distance(self.center, self.target) # type: float 623 624 def render(self, layout: 'GenericLayoutType') -> None: 625 """ build dimension line object with basic dxf entities """ 626 self._setup() 627 self._draw_dimension_line(layout) 628 self._draw_dimension_text(layout) 629 self._draw_ticks(layout) 630 631 def _draw_dimension_line(self, layout: 'GenericLayoutType') -> None: 632 start_point = self.center + (self.target_vector * (self.radius - self.length)) 633 layout.add_line( 634 start=start_point, end=self.target, 635 dxfattribs={ 636 'color': self.prop('dimlinecolor'), 637 'layer': self.prop('layer'), 638 }, 639 ) 640 641 def _draw_dimension_text(self, layout: 'GenericLayoutType') -> None: 642 layout.add_text( 643 text=self._get_dimtext(), 644 dxfattribs={ 645 'height': self.prop('height'), 646 'rotation': self.target_vector.angle_deg, 647 'layer': self.prop('layer'), 648 'style': self.prop('style'), 649 'color': self.prop('textcolor'), 650 } 651 ).set_pos(self._get_insert_point(), align='MIDDLE_RIGHT') 652 653 def _get_insert_point(self) -> Vec3: 654 return self.target - (self.target_vector * (self.length + self.prop('textabove'))) 655 656 def _get_dimtext(self) -> str: 657 return self.format_dimtext(self.radius * self.prop('scale')) 658 659 def _draw_ticks(self, layout: 'GenericLayoutType') -> None: 660 layout.add_blockref( 661 insert=self.target, 662 name='DIMTICK_RADIUS', 663 dxfattribs={ 664 'rotation': self.target_vector.angle_deg + 180, 665 'xscale': self.prop('tickfactor'), 666 'yscale': self.prop('tickfactor'), 667 'layer': self.prop('layer'), 668 } 669 ) 670 671 672def center_of_3points_arc(point1: 'Vertex', point2: 'Vertex', point3: 'Vertex') -> Vec3: 673 """ 674 Calc center point of 3 point arc. ConstructionCircle is defined by 3 points on the circle: point1, point2 and point3. 675 """ 676 ray1 = ConstructionRay(point1, point2) 677 ray2 = ConstructionRay(point1, point3) 678 midpoint1 = lerp(point1, point2) 679 midpoint2 = lerp(point1, point3) 680 center_ray1 = ray1.orthogonal(midpoint1) 681 center_ray2 = ray2.orthogonal(midpoint2) 682 return center_ray1.intersect(center_ray2) 683