r12writer.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814
  1. # Purpose: fast & simple but restricted DXF R12 writer, with no in-memory drawing, and without dependencies to other
  2. # ezdxf modules. The created DXF file contains no HEADER, TABLES or BLOCKS section only the ENTITIES section is present.
  3. # Created: 14.04.2016
  4. # Copyright (C) 2016, Manfred Moitzi
  5. # License: MIT License
  6. from typing import TextIO, Union, Sequence, Iterable
  7. from contextlib import contextmanager
  8. # types
  9. Vertex = Sequence[float]
  10. def rnd(x: float) -> float: # adjust output precision of floats by changing 'ndigits'
  11. return round(x, ndigits=6)
  12. TEXT_ALIGN_FLAGS = {
  13. 'LEFT': (0, 0),
  14. 'CENTER': (1, 0),
  15. 'RIGHT': (2, 0),
  16. 'BOTTOM_LEFT': (0, 1),
  17. 'BOTTOM_CENTER': (1, 1),
  18. 'BOTTOM_RIGHT': (2, 1),
  19. 'MIDDLE_LEFT': (0, 2),
  20. 'MIDDLE_CENTER': (1, 2),
  21. 'MIDDLE_RIGHT': (2, 2),
  22. 'TOP_LEFT': (0, 3),
  23. 'TOP_CENTER': (1, 3),
  24. 'TOP_RIGHT': (2, 3),
  25. }
  26. @contextmanager
  27. def r12writer(stream: Union[TextIO, str], fixed_tables=False) -> 'R12FastStreamWriter':
  28. if hasattr(stream, 'write'):
  29. writer = R12FastStreamWriter(stream, fixed_tables)
  30. try:
  31. yield writer
  32. finally:
  33. writer.close()
  34. else:
  35. with open(stream, 'wt') as stream:
  36. writer = R12FastStreamWriter(stream, fixed_tables)
  37. try:
  38. yield writer
  39. finally:
  40. writer.close()
  41. class R12FastStreamWriter:
  42. def __init__(self, stream: TextIO, fixed_tables=False):
  43. self.stream = stream
  44. if fixed_tables:
  45. stream.write(PREFACE)
  46. stream.write("0\nSECTION\n2\nENTITIES\n") # write header
  47. def close(self) -> None:
  48. self.stream.write("0\nENDSEC\n0\nEOF\n") # write tail
  49. def add_line(self,
  50. start: Vertex,
  51. end: Vertex,
  52. layer: str = "0",
  53. color: int = None,
  54. linetype: str = None) -> None:
  55. dxf = ["0\nLINE\n"]
  56. dxf.append(dxf_attribs(layer, color, linetype))
  57. dxf.append(dxf_vertex(start, code=10))
  58. dxf.append(dxf_vertex(end, code=11))
  59. self.stream.write(''.join(dxf))
  60. def add_circle(self,
  61. center: Vertex,
  62. radius: float,
  63. layer: str = "0",
  64. color: int = None,
  65. linetype: str = None) -> None:
  66. dxf = ["0\nCIRCLE\n"]
  67. dxf.append(dxf_attribs(layer, color, linetype))
  68. dxf.append(dxf_vertex(center))
  69. dxf.append(dxf_tag(40, str(rnd(radius))))
  70. self.stream.write(''.join(dxf))
  71. def add_arc(self,
  72. center: Vertex,
  73. radius: float,
  74. start: float = 0,
  75. end: float = 360,
  76. layer: str = "0",
  77. color: int = None,
  78. linetype: str = None) -> None:
  79. dxf = ["0\nARC\n"]
  80. dxf.append(dxf_attribs(layer, color, linetype))
  81. dxf.append(dxf_vertex(center))
  82. dxf.append(dxf_tag(40, str(rnd(radius))))
  83. dxf.append(dxf_tag(50, str(rnd(start))))
  84. dxf.append(dxf_tag(51, str(rnd(end))))
  85. self.stream.write(''.join(dxf))
  86. def add_point(self,
  87. location: Vertex,
  88. layer: str = "0",
  89. color: int = None,
  90. linetype: str = None) -> None:
  91. dxf = ["0\nPOINT\n"]
  92. dxf.append(dxf_attribs(layer, color, linetype))
  93. dxf.append(dxf_vertex(location))
  94. self.stream.write(''.join(dxf))
  95. def add_3dface(self,
  96. vertices: Iterable[Vertex],
  97. invisible: int = 0,
  98. layer: str = "0",
  99. color: int = None,
  100. linetype: str = None) -> None:
  101. self._add_quadrilateral('3DFACE', vertices, invisible, layer, color, linetype)
  102. def add_solid(self,
  103. vertices: Iterable[Vertex],
  104. layer: str = "0",
  105. color: int = None,
  106. linetype: str = None) -> None:
  107. self._add_quadrilateral('SOLID', vertices, 0, layer, color, linetype)
  108. def _add_quadrilateral(self,
  109. dxftype: str,
  110. vertices: Iterable[Vertex],
  111. flags: int,
  112. layer: str,
  113. color: int,
  114. linetype: str) -> None:
  115. dxf = ["0\n%s\n" % dxftype]
  116. dxf.append(dxf_attribs(layer, color, linetype))
  117. vertices = list(vertices)
  118. if len(vertices) < 3:
  119. raise ValueError("%s needs 3 or 4 vertices." % dxftype)
  120. elif len(vertices) == 3:
  121. vertices.append(vertices[-1]) # double last vertex
  122. dxf.extend(dxf_vertex(vertex, code) for code, vertex in enumerate(vertices, start=10))
  123. if flags:
  124. dxf.append(dxf_tag(70, str(flags)))
  125. self.stream.write(''.join(dxf))
  126. def add_polyline(self,
  127. vertices: Iterable[Vertex],
  128. layer: str = "0",
  129. color: int = None,
  130. linetype: str = None) -> None:
  131. def write_polyline(flags: int) -> None:
  132. dxf = ["0\nPOLYLINE\n"]
  133. dxf.append(dxf_attribs(layer, color, linetype))
  134. dxf.append(dxf_tag(66, 1)) # entities follow
  135. dxf.append(dxf_tag(70, flags))
  136. self.stream.write(''.join(dxf))
  137. polyline_flags, vertex_flags = None, None
  138. for vertex in vertices:
  139. if polyline_flags is None: # first vertex
  140. if len(vertex) == 3: # 3d polyline
  141. polyline_flags, vertex_flags = (8, 32)
  142. else: # 2d polyline
  143. polyline_flags, vertex_flags = (0, 0)
  144. write_polyline(polyline_flags)
  145. dxf = ["0\nVERTEX\n"]
  146. dxf.append(dxf_attribs(layer))
  147. dxf.append(dxf_tag(70, vertex_flags))
  148. dxf.append(dxf_vertex(vertex))
  149. self.stream.write(''.join(dxf))
  150. if polyline_flags is not None:
  151. self.stream.write("0\nSEQEND\n")
  152. def add_text(self,
  153. text: str,
  154. insert: Vertex = (0, 0),
  155. height: float = 1.,
  156. width: float = 1.,
  157. align: str = "LEFT",
  158. rotation: float = 0.,
  159. oblique: float = 0.,
  160. style: str = 'STANDARD',
  161. layer: str = "0",
  162. color: int = None) -> None:
  163. # text style is always STANDARD without a TABLES section
  164. dxf = ["0\nTEXT\n"]
  165. dxf.append(dxf_attribs(layer, color))
  166. dxf.append(dxf_vertex(insert, code=10))
  167. dxf.append(dxf_tag(1, str(text)))
  168. dxf.append(dxf_tag(40, str(rnd(height))))
  169. if width != 1.:
  170. dxf.append(dxf_tag(41, str(rnd(width))))
  171. if rotation != 0.:
  172. dxf.append(dxf_tag(50, str(rnd(rotation))))
  173. if oblique != 0.:
  174. dxf.append(dxf_tag(51, str(rnd(oblique))))
  175. if style != "STANDARD":
  176. dxf.append(dxf_tag(7, str(style)))
  177. halign, valign = TEXT_ALIGN_FLAGS[align.upper()]
  178. dxf.append(dxf_tag(72, str(halign)))
  179. dxf.append(dxf_tag(73, str(valign)))
  180. dxf.append(dxf_vertex(insert, code=11)) # align point
  181. self.stream.write(''.join(dxf))
  182. def dxf_attribs(layer: str, color: int = None, linetype: str = None) -> str:
  183. dxf = ["8\n%s\n" % layer] # layer is required
  184. if linetype is not None:
  185. dxf.append("6\n%s\n" % linetype)
  186. if color is not None:
  187. if 0 <= int(color) < 257:
  188. dxf.append("62\n%d\n" % color)
  189. else:
  190. raise ValueError("color has to be an integer in the range from 0 to 256.")
  191. return "".join(dxf)
  192. def dxf_vertex(vertex: Vertex, code=10) -> str:
  193. dxf = []
  194. for c in vertex:
  195. dxf.append("%d\n%s\n" % (code, str(rnd(c))))
  196. code += 10
  197. return "".join(dxf)
  198. def dxf_tag(code: int, value) -> str:
  199. return "%d\n%s\n" % (code, value)
  200. PREFACE = """ 0
  201. SECTION
  202. 2
  203. HEADER
  204. 9
  205. $ACADVER
  206. 1
  207. AC1009
  208. 9
  209. $DWGCODEPAGE
  210. 3
  211. ANSI_1252
  212. 0
  213. ENDSEC
  214. 0
  215. SECTION
  216. 2
  217. TABLES
  218. 0
  219. TABLE
  220. 2
  221. LTYPE
  222. 5
  223. 431
  224. 70
  225. 20
  226. 0
  227. LTYPE
  228. 5
  229. 40F
  230. 2
  231. CONTINUOUS
  232. 70
  233. 0
  234. 3
  235. Solid line
  236. 72
  237. 65
  238. 73
  239. 0
  240. 40
  241. 0.0
  242. 0
  243. LTYPE
  244. 5
  245. 410
  246. 2
  247. CENTER
  248. 70
  249. 0
  250. 3
  251. Center ____ _ ____ _ ____ _ ____ _ ____ _ ____
  252. 72
  253. 65
  254. 73
  255. 4
  256. 40
  257. 2.0
  258. 49
  259. 1.25
  260. 49
  261. -0.25
  262. 49
  263. 0.25
  264. 49
  265. -0.25
  266. 0
  267. LTYPE
  268. 5
  269. 411
  270. 2
  271. DASHED
  272. 70
  273. 0
  274. 3
  275. Dashed __ __ __ __ __ __ __ __ __ __ __ __ __ _
  276. 72
  277. 65
  278. 73
  279. 2
  280. 40
  281. 0.75
  282. 49
  283. 0.5
  284. 49
  285. -0.25
  286. 0
  287. LTYPE
  288. 5
  289. 412
  290. 2
  291. PHANTOM
  292. 70
  293. 0
  294. 3
  295. Phantom ______ __ __ ______ __ __ ______
  296. 72
  297. 65
  298. 73
  299. 6
  300. 40
  301. 2.5
  302. 49
  303. 1.25
  304. 49
  305. -0.25
  306. 49
  307. 0.25
  308. 49
  309. -0.25
  310. 49
  311. 0.25
  312. 49
  313. -0.25
  314. 0
  315. LTYPE
  316. 5
  317. 413
  318. 2
  319. HIDDEN
  320. 70
  321. 0
  322. 3
  323. Hidden __ __ __ __ __ __ __ __ __ __ __ __ __ __
  324. 72
  325. 65
  326. 73
  327. 2
  328. 40
  329. 9.525
  330. 49
  331. 6.345
  332. 49
  333. -3.175
  334. 0
  335. LTYPE
  336. 5
  337. 43B
  338. 2
  339. CENTERX2
  340. 70
  341. 0
  342. 3
  343. Center (2x) ________ __ ________ __ ________
  344. 72
  345. 65
  346. 73
  347. 4
  348. 40
  349. 3.5
  350. 49
  351. 2.5
  352. 49
  353. -0.25
  354. 49
  355. 0.5
  356. 49
  357. -0.25
  358. 0
  359. LTYPE
  360. 5
  361. 43C
  362. 2
  363. CENTER2
  364. 70
  365. 0
  366. 3
  367. Center (.5x) ____ _ ____ _ ____ _ ____ _ ____
  368. 72
  369. 65
  370. 73
  371. 4
  372. 40
  373. 1.0
  374. 49
  375. 0.625
  376. 49
  377. -0.125
  378. 49
  379. 0.125
  380. 49
  381. -0.125
  382. 0
  383. LTYPE
  384. 5
  385. 43D
  386. 2
  387. DASHEDX2
  388. 70
  389. 0
  390. 3
  391. Dashed (2x) ____ ____ ____ ____ ____ ____
  392. 72
  393. 65
  394. 73
  395. 2
  396. 40
  397. 1.2
  398. 49
  399. 1.0
  400. 49
  401. -0.2
  402. 0
  403. LTYPE
  404. 5
  405. 43E
  406. 2
  407. DASHED2
  408. 70
  409. 0
  410. 3
  411. Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _
  412. 72
  413. 65
  414. 73
  415. 2
  416. 40
  417. 0.3
  418. 49
  419. 0.25
  420. 49
  421. -0.05
  422. 0
  423. LTYPE
  424. 5
  425. 43F
  426. 2
  427. PHANTOMX2
  428. 70
  429. 0
  430. 3
  431. Phantom (2x)____________ ____ ____ ____________
  432. 72
  433. 65
  434. 73
  435. 6
  436. 40
  437. 4.25
  438. 49
  439. 2.5
  440. 49
  441. -0.25
  442. 49
  443. 0.5
  444. 49
  445. -0.25
  446. 49
  447. 0.5
  448. 49
  449. -0.25
  450. 0
  451. LTYPE
  452. 5
  453. 440
  454. 2
  455. PHANTOM2
  456. 70
  457. 0
  458. 3
  459. Phantom (.5x) ___ _ _ ___ _ _ ___ _ _ ___ _ _ ___
  460. 72
  461. 65
  462. 73
  463. 6
  464. 40
  465. 1.25
  466. 49
  467. 0.625
  468. 49
  469. -0.125
  470. 49
  471. 0.125
  472. 49
  473. -0.125
  474. 49
  475. 0.125
  476. 49
  477. -0.125
  478. 0
  479. LTYPE
  480. 5
  481. 441
  482. 2
  483. DASHDOT
  484. 70
  485. 0
  486. 3
  487. Dash dot __ . __ . __ . __ . __ . __ . __ . __
  488. 72
  489. 65
  490. 73
  491. 4
  492. 40
  493. 1.4
  494. 49
  495. 1.0
  496. 49
  497. -0.2
  498. 49
  499. 0.0
  500. 49
  501. -0.2
  502. 0
  503. LTYPE
  504. 5
  505. 442
  506. 2
  507. DASHDOTX2
  508. 70
  509. 0
  510. 3
  511. Dash dot (2x) ____ . ____ . ____ . ____
  512. 72
  513. 65
  514. 73
  515. 4
  516. 40
  517. 2.4
  518. 49
  519. 2.0
  520. 49
  521. -0.2
  522. 49
  523. 0.0
  524. 49
  525. -0.2
  526. 0
  527. LTYPE
  528. 5
  529. 443
  530. 2
  531. DASHDOT2
  532. 70
  533. 0
  534. 3
  535. Dash dot (.5x) _ . _ . _ . _ . _ . _ . _ . _
  536. 72
  537. 65
  538. 73
  539. 4
  540. 40
  541. 0.7
  542. 49
  543. 0.5
  544. 49
  545. -0.1
  546. 49
  547. 0.0
  548. 49
  549. -0.1
  550. 0
  551. LTYPE
  552. 5
  553. 444
  554. 2
  555. DOT
  556. 70
  557. 0
  558. 3
  559. Dot . . . . . . . . . . . . . . . .
  560. 72
  561. 65
  562. 73
  563. 2
  564. 40
  565. 0.2
  566. 49
  567. 0.0
  568. 49
  569. -0.2
  570. 0
  571. LTYPE
  572. 5
  573. 445
  574. 2
  575. DOTX2
  576. 70
  577. 0
  578. 3
  579. Dot (2x) . . . . . . . .
  580. 72
  581. 65
  582. 73
  583. 2
  584. 40
  585. 0.4
  586. 49
  587. 0.0
  588. 49
  589. -0.4
  590. 0
  591. LTYPE
  592. 5
  593. 446
  594. 2
  595. DOT2
  596. 70
  597. 0
  598. 3
  599. Dot (.5) . . . . . . . . . . . . . . . . . . .
  600. 72
  601. 65
  602. 73
  603. 2
  604. 40
  605. 0.1
  606. 49
  607. 0.0
  608. 49
  609. -0.1
  610. 0
  611. LTYPE
  612. 5
  613. 447
  614. 2
  615. DIVIDE
  616. 70
  617. 0
  618. 3
  619. Divide __ . . __ . . __ . . __ . . __ . . __
  620. 72
  621. 65
  622. 73
  623. 6
  624. 40
  625. 1.6
  626. 49
  627. 1.0
  628. 49
  629. -0.2
  630. 49
  631. 0.0
  632. 49
  633. -0.2
  634. 49
  635. 0.0
  636. 49
  637. -0.2
  638. 0
  639. LTYPE
  640. 5
  641. 448
  642. 2
  643. DIVIDEX2
  644. 70
  645. 0
  646. 3
  647. Divide (2x) ____ . . ____ . . ____ . . ____
  648. 72
  649. 65
  650. 73
  651. 6
  652. 40
  653. 2.6
  654. 49
  655. 2.0
  656. 49
  657. -0.2
  658. 49
  659. 0.0
  660. 49
  661. -0.2
  662. 49
  663. 0.0
  664. 49
  665. -0.2
  666. 0
  667. LTYPE
  668. 5
  669. 449
  670. 2
  671. DIVIDE2
  672. 70
  673. 0
  674. 3
  675. Divide(.5x) _ . _ . _ . _ . _ . _ . _ . _
  676. 72
  677. 65
  678. 73
  679. 6
  680. 40
  681. 0.8
  682. 49
  683. 0.5
  684. 49
  685. -0.1
  686. 49
  687. 0.0
  688. 49
  689. -0.1
  690. 49
  691. 0.0
  692. 49
  693. -0.1
  694. 0
  695. ENDTAB
  696. 0
  697. TABLE
  698. 2
  699. STYLE
  700. 5
  701. 433
  702. 70
  703. 18
  704. 0
  705. STYLE
  706. 5
  707. 417
  708. 2
  709. STANDARD
  710. 70
  711. 0
  712. 40
  713. 0.0
  714. 41
  715. 1.0
  716. 50
  717. 0.0
  718. 71
  719. 0
  720. 42
  721. 0.2
  722. 3
  723. txt
  724. 4
  725. 0
  726. STYLE
  727. 5
  728. 44A
  729. 2
  730. OpenSans
  731. 70
  732. 0
  733. 40
  734. 0.0
  735. 41
  736. 1.0
  737. 50
  738. 0.0
  739. 71
  740. 0
  741. 42
  742. 1.0
  743. 3
  744. OpenSans-Regular.ttf
  745. 4
  746. 0
  747. STYLE
  748. 5
  749. 44F
  750. 2
  751. OpenSansCondensed-Light
  752. 70
  753. 0
  754. 40
  755. 0.0
  756. 41
  757. 1.0
  758. 50
  759. 0.0
  760. 71
  761. 0
  762. 42
  763. 1.0
  764. 3
  765. OpenSansCondensed-Light.ttf
  766. 4
  767. 0
  768. ENDTAB
  769. 0
  770. TABLE
  771. 2
  772. VIEW
  773. 5
  774. 434
  775. 70
  776. 0
  777. 0
  778. ENDTAB
  779. 0
  780. ENDSEC
  781. """