dimlines.py 26 KB

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