repair.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. # Purpose: repair/setup required DXF structures in existing DXF files (created by other DXF libs)
  2. # Created: 05.03.2016
  3. # Copyright (C) 2016, Manfred Moitzi
  4. # License: MIT License
  5. # --------------------------------------------------- #
  6. # Welcome to the place, where it gets dirty and ugly! #
  7. # --------------------------------------------------- #
  8. from typing import TYPE_CHECKING, Iterable, Optional, List
  9. from functools import partial
  10. import logging
  11. from .extendedtags import ExtendedTags
  12. from .tags import DXFTag, Tags
  13. from .const import DXFInternalEzdxfError, DXFValueError, DXFKeyError, SUBCLASS_MARKER
  14. logger = logging.getLogger('ezdxf')
  15. if TYPE_CHECKING: # import forward declarations
  16. from ezdxf.eztypes import Drawing
  17. def setup_layouts(dwg: 'Drawing'):
  18. layout_dict = dwg.rootdict.get_required_dict('ACAD_LAYOUT')
  19. if 'Model' not in layout_dict: # do it only if model space is not defined
  20. setup_model_space(dwg)
  21. setup_paper_space(dwg)
  22. def setup_model_space(dwg: 'Drawing'):
  23. setup_layout_space(dwg, 'Model', '*Model_Space', _MODEL_SPACE_LAYOUT_TPL)
  24. def setup_paper_space(dwg: 'Drawing'):
  25. setup_layout_space(dwg, 'Layout1', '*Paper_Space', _PAPER_SPACE_LAYOUT_TPL)
  26. def setup_layout_space(dwg: 'Drawing', layout_name: str, block_name: str, tag_string: str):
  27. # This is just necessary for existing DXF drawings without properly setup management structures.
  28. # Layout structure is not initialized at this runtime phase
  29. logger.info('creating LAYOUT structure for: {}'.format(layout_name))
  30. def get_block_record_by_alt_names(names: Iterable[str]):
  31. for name in names:
  32. try:
  33. brecord = dwg.block_records.get(name)
  34. except DXFValueError:
  35. pass
  36. else:
  37. return brecord
  38. raise DXFKeyError
  39. layout_dict = dwg.rootdict.get_required_dict('ACAD_LAYOUT')
  40. if layout_name in layout_dict:
  41. return
  42. try:
  43. block_record = get_block_record_by_alt_names((block_name, block_name.upper()))
  44. except DXFKeyError:
  45. raise NotImplementedError("'%s' block record setup not implemented, send an email to "
  46. "<ezdxf@mozman.at> with your DXF file." % block_name)
  47. real_block_name = block_record.dxf.name # can be *Model_Space or *MODEL_SPACE
  48. block_record_handle = block_record.dxf.handle
  49. logger.debug('found {}: {}'.format(str(block_record), real_block_name))
  50. try:
  51. block_layout = dwg.blocks.get(real_block_name)
  52. logger.debug('found {}: {}'.format(str(block_layout.block), real_block_name))
  53. except DXFKeyError:
  54. logger.debug('expected BLOCK: {} not found.'.format(real_block_name))
  55. raise NotImplementedError("'%s' block setup not implemented, send an email to "
  56. "<ezdxf@mozman.at> with your DXF file." % real_block_name)
  57. else:
  58. block_layout.set_block_record_handle(block_record_handle) # grant valid linking
  59. layout_handle = create_layout_tags(dwg, block_record_handle, owner=layout_dict.dxf.handle, tag_string=tag_string)
  60. logger.debug('creating entry in ACAD_LAYOUT dictionary for {}'.format(layout_name))
  61. layout_dict[layout_name] = layout_handle # insert layout into the layout management table
  62. block_record.dxf.layout = layout_handle # link model space block record to layout
  63. # rename block to standard format (*Model_Space or *Paper_Space)
  64. if real_block_name != block_name:
  65. logger.debug('renaming BLOCK form {} to {}'.format(block_name, real_block_name))
  66. dwg.blocks.rename_block(real_block_name, block_name)
  67. def create_layout_tags(dwg: 'Drawing', block_record_handle: str, owner: str, tag_string: str):
  68. # Problem: ezdxf was not designed to handle the absence of model/paper space LAYOUT entities
  69. # Layout structure is not initialized at this runtime phase
  70. logger.debug('creating LAYOUT entity for BLOCK_RECORD(#{})'.format(block_record_handle))
  71. object_section = dwg.objects
  72. entitydb = dwg.entitydb
  73. tags = ExtendedTags.from_text(tag_string)
  74. layout_handle = entitydb.get_unique_handle() # create new unique handle
  75. tags.replace_handle(layout_handle) # set entity handle
  76. entitydb.add_tags(tags) # add layout entity to entity database
  77. object_section.add_handle(layout_handle) # add layout entity to objects section
  78. tags.noclass.set_first(DXFTag(330, owner)) # set owner tag
  79. acdblayout = tags.get_subclass('AcDbLayout')
  80. acdblayout.set_first(DXFTag(330, block_record_handle)) # link to block record
  81. return layout_handle
  82. def upgrade_to_ac1015(dwg: 'Drawing'):
  83. """
  84. Upgrade DXF versions AC1012 and AC1014 to AC1015.
  85. """
  86. def upgrade_layout_table():
  87. if 'ACAD_LAYOUT' in dwg.rootdict:
  88. setup_model_space(dwg) # setup layout entity and link to proper block and block_record entities
  89. setup_paper_space(dwg) # setup layout entity and link to proper block and block_record entities
  90. else:
  91. raise DXFInternalEzdxfError("Table ACAD_LAYOUT should already exist in root dict.")
  92. def upgrade_layer_table():
  93. logger.debug('upgrading LAYERS table')
  94. try:
  95. plot_style_name_handle = dwg.rootdict.get('ACAD_PLOTSTYLENAME') # DXFDictionaryWithDefault
  96. except DXFKeyError:
  97. raise DXFInternalEzdxfError("Table ACAD_PLOTSTYLENAME should already exist in root dict.")
  98. set_plot_style_name_in_layers(plot_style_name_handle)
  99. try: # do not plot DEFPOINTS layer or AutoCAD is yelling
  100. defpoints_layer = dwg.layers.get('DEFPOINTS')
  101. except DXFValueError:
  102. pass
  103. else:
  104. defpoints_layer.dxf.plot = 0
  105. def set_plot_style_name_in_layers(plot_style_name_handle):
  106. logger.debug('setting layers "plot_style_name" attribute')
  107. for layer in dwg.layers:
  108. layer.dxf.plot_style_name = plot_style_name_handle
  109. def upgrade_dim_style_table():
  110. logger.debug('upgrading DIMSTYLES table')
  111. dim_styles = dwg.dimstyles
  112. header = dim_styles._table_header
  113. dim_style_table = Tags([
  114. DXFTag(100, 'AcDbDimStyleTable'),
  115. DXFTag(71, len(dim_styles))
  116. ])
  117. for entry in dim_styles:
  118. dim_style_table.append(DXFTag(340, entry.dxf.handle))
  119. header.subclasses.append(dim_style_table)
  120. def upgrade_objects():
  121. logger.debug('upgrading ACDBPLACEHOLDER entities in the OBJECTS section')
  122. upgrade_acdbplaceholder(dwg.objects.query('ACDBPLACEHOLDER'))
  123. def upgrade_acdbplaceholder(entities):
  124. for entity in entities:
  125. entity.tags.subclasses = entity.tags.subclasses[0:1] # remove subclass AcDbPlaceHolder
  126. # calling order is important!
  127. logger.info('Upgrading drawing to DXF R2000.')
  128. upgrade_layout_table()
  129. upgrade_layer_table()
  130. upgrade_dim_style_table()
  131. upgrade_objects()
  132. logger.debug('Setting DXF version to AC1015.')
  133. dwg.dxfversion = 'AC1015'
  134. dwg.header['$ACADVER'] = 'AC1015'
  135. def upgrade_to_ac1009(dwg: 'Drawing'):
  136. """
  137. Upgrade DXF versions prior to AC1009 (R12) to AC1009.
  138. """
  139. logger.info('Upgrading drawing to DXF R12.')
  140. logger.debug('Setting DXF version to AC1009.')
  141. dwg.dxfversion = 'AC1009'
  142. dwg.header['$ACADVER'] = 'AC1009'
  143. # as far I know, nothing else to do
  144. def cleanup_r12(dwg: 'Drawing'):
  145. """
  146. Remove unsupported sections and tables, repair tag structure.
  147. Args:
  148. dwg: Drawing() object
  149. """
  150. logger.info('Cleanup DXF R12 drawing.')
  151. if dwg.dxfversion > 'AC1009':
  152. return
  153. for section_name in ('CLASSES', 'OBJECTS', 'THUMBNAILIMAGE', 'ACDSDATA'): # unsupported sections for DXF R12
  154. if section_name in dwg.sections:
  155. logger.debug('Deleting {} section.'.format(section_name))
  156. dwg.sections.delete_section(section_name)
  157. if 'BLOCK_RECORDS' in dwg.sections.tables:
  158. logger.debug('Deleting BLOCK_RECORDS table.')
  159. del dwg.sections.tables['BLOCK_RECORDS']
  160. def filter_subclass_marker(tagger: Iterable[DXFTag]) -> Iterable[DXFTag]:
  161. """
  162. Filter subclass marker from malformed DXF R12 files. (like from Leica Disto Units)
  163. Subclass markers in R12 files, creates subclasses in ExtendedTags(), which does not work with the DXF R12 attribute
  164. definitions. Other unsupported tags are not problematic, they are just ignored.
  165. Args:
  166. tagger: low level tagger
  167. """
  168. found = 0
  169. for tag in tagger:
  170. if tag.code == SUBCLASS_MARKER and tag.value.startswith('AcDb'):
  171. found += 1
  172. else:
  173. yield tag
  174. if found:
  175. logger.debug('Filtered {} SUBCLASS marker from DXF R12 tag stream.'.format(found))
  176. def tag_reorder_layer(tagger: Iterable[DXFTag]) -> Iterable[DXFTag]:
  177. """
  178. Reorder coordinates of legacy DXF Entities, for now only LINE.
  179. Args:
  180. tagger: low level tagger
  181. """
  182. logger.debug('Reordering coordinate tags for LINE entity.')
  183. collector = None # type: Optional[List]
  184. for tag in tagger:
  185. if tag.code == 0:
  186. if collector is not None: # stop collecting if inside of an supported entity
  187. entity = collector[0].value
  188. yield from COORDINATE_FIXING_TOOLBOX[entity](collector)
  189. collector = None
  190. if tag.value in COORDINATE_FIXING_TOOLBOX:
  191. collector = [tag]
  192. tag = None # do not yield collected tag yet
  193. else: # tag.code != 0
  194. if collector is not None:
  195. collector.append(tag)
  196. tag = None # do not yield collected tag yet
  197. if tag is not None:
  198. yield tag
  199. def fix_coordinate_order(tags, codes=(10, 11)):
  200. def extend_codes():
  201. for code in codes:
  202. yield code # x tag
  203. yield code + 10 # y tag
  204. yield code + 20 # z tag
  205. def get_coords(code):
  206. # if x or y coordinate is missing, it is a DXFStructureError
  207. # but here is not the location to validate the DXF structure
  208. try:
  209. yield coordinates[code]
  210. except KeyError:
  211. pass
  212. try:
  213. yield coordinates[code + 10]
  214. except KeyError:
  215. pass
  216. try:
  217. yield coordinates[code + 20]
  218. except KeyError:
  219. pass
  220. coordinate_codes = frozenset(extend_codes())
  221. coordinates = {}
  222. remaining_tags = []
  223. insert_pos = None
  224. for tag in tags:
  225. # separate tags
  226. if tag.code in coordinate_codes:
  227. coordinates[tag.code] = tag
  228. if insert_pos is None:
  229. insert_pos = tags.index(tag)
  230. else:
  231. remaining_tags.append(tag)
  232. if len(coordinates) == 0:
  233. # no coordinates found, this is probably a DXFStructureError,
  234. # but here is not the location to validate the DXF structure,
  235. # just do nothing.
  236. return tags
  237. ordered_coords = []
  238. for code in codes:
  239. ordered_coords.extend(get_coords(code))
  240. remaining_tags[insert_pos:insert_pos] = ordered_coords
  241. return remaining_tags
  242. COORDINATE_FIXING_TOOLBOX = {
  243. 'LINE': partial(fix_coordinate_order, codes=(10, 11)),
  244. }
  245. def fix_classes(dwg):
  246. def remove_group_code_91():
  247. logger.debug('Deleting group code 91 tags from CLASS entities for DXF Versions prior AC1018.')
  248. for cls in dwg.sections.classes:
  249. xtags = cls.tags
  250. xtags.noclass.remove_tags((91,))
  251. if dwg.dxfversion <= 'AC1009': # DXF R12 and prior has no CLASSES
  252. return
  253. if dwg.dxfversion < 'AC1018':
  254. # remove group code 91, which is not supported prior to AC1018
  255. remove_group_code_91()
  256. _MODEL_SPACE_LAYOUT_TPL = """ 0
  257. LAYOUT
  258. 5
  259. 0
  260. 330
  261. 0
  262. 100
  263. AcDbPlotSettings
  264. 1
  265. 2
  266. DWFx ePlot (XPS Compatible).pc3
  267. 4
  268. ANSI_A_(8.50_x_11.00_Inches)
  269. 6
  270. 40
  271. 5.8
  272. 41
  273. 17.8
  274. 42
  275. 5.8
  276. 43
  277. 17.8
  278. 44
  279. 215.9
  280. 45
  281. 279.4
  282. 46
  283. 0.0
  284. 47
  285. 0.0
  286. 48
  287. 0.0
  288. 49
  289. 0.0
  290. 140
  291. 0.0
  292. 141
  293. 0.0
  294. 142
  295. 1.0
  296. 143
  297. 14.53
  298. 70
  299. 11952
  300. 72
  301. 0
  302. 73
  303. 1
  304. 74
  305. 0
  306. 7
  307. 75
  308. 0
  309. 147
  310. 0.069
  311. 148
  312. 114.98
  313. 149
  314. 300.29
  315. 100
  316. AcDbLayout
  317. 1
  318. Model
  319. 70
  320. 1
  321. 71
  322. 0
  323. 10
  324. 0.0
  325. 20
  326. 0.0
  327. 11
  328. 12.0
  329. 21
  330. 9.0
  331. 12
  332. 0.0
  333. 22
  334. 0.0
  335. 32
  336. 0.0
  337. 14
  338. 0.0
  339. 24
  340. 0.0
  341. 34
  342. 0.0
  343. 15
  344. 0.0
  345. 25
  346. 0.0
  347. 35
  348. 0.0
  349. 146
  350. 0.0
  351. 13
  352. 0.0
  353. 23
  354. 0.0
  355. 33
  356. 0.0
  357. 16
  358. 1.0
  359. 26
  360. 0.0
  361. 36
  362. 0.0
  363. 17
  364. 0.0
  365. 27
  366. 1.0
  367. 37
  368. 0.0
  369. 76
  370. 0
  371. 330
  372. 0
  373. """
  374. _PAPER_SPACE_LAYOUT_TPL = """ 0
  375. LAYOUT
  376. 5
  377. DEAD
  378. 330
  379. DEAD
  380. 100
  381. AcDbPlotSettings
  382. 1
  383. 2
  384. DWFx ePlot (XPS Compatible).pc3
  385. 4
  386. ANSI_A_(8.50_x_11.00_Inches)
  387. 6
  388. 40
  389. 5.8
  390. 41
  391. 17.8
  392. 42
  393. 5.8
  394. 43
  395. 17.8
  396. 44
  397. 215.9
  398. 45
  399. 279.4
  400. 46
  401. 0.0
  402. 47
  403. 0.0
  404. 48
  405. 0.0
  406. 49
  407. 0.0
  408. 140
  409. 0.0
  410. 141
  411. 0.0
  412. 142
  413. 1.0
  414. 143
  415. 1.0
  416. 70
  417. 688
  418. 72
  419. 0
  420. 73
  421. 1
  422. 74
  423. 5
  424. 7
  425. acad.ctb
  426. 75
  427. 16
  428. 147
  429. 1.0
  430. 148
  431. 0.0
  432. 149
  433. 0.0
  434. 100
  435. AcDbLayout
  436. 1
  437. Layout1
  438. 70
  439. 1
  440. 71
  441. 1
  442. 10
  443. -0.7
  444. 20
  445. -0.23
  446. 11
  447. 10.3
  448. 21
  449. 8.27
  450. 12
  451. 0.0
  452. 22
  453. 0.0
  454. 32
  455. 0.0
  456. 14
  457. 0.63
  458. 24
  459. 0.8
  460. 34
  461. 0.0
  462. 15
  463. 9.0
  464. 25
  465. 7.2
  466. 35
  467. 0.0
  468. 146
  469. 0.0
  470. 13
  471. 0.0
  472. 23
  473. 0.0
  474. 33
  475. 0.0
  476. 16
  477. 1.0
  478. 26
  479. 0.0
  480. 36
  481. 0.0
  482. 17
  483. 0.0
  484. 27
  485. 1.0
  486. 37
  487. 0.0
  488. 76
  489. 0
  490. 330
  491. DEAD
  492. """