dxfpp.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. # Purpose: Create a structured HTML view of the DXF tags - not a CAD drawing!
  2. # Created: 20.05.13
  3. # Copyright (c) 2013-2018, Manfred Moitzi
  4. # License: MIT License
  5. """
  6. Creates a structured HTML view of the DXF tags - not a CAD drawing!
  7. """
  8. import os
  9. from typing import TYPE_CHECKING, Sequence, Tuple, Iterable, Dict, Set
  10. from ezdxf.lldxf.types import tag_type, is_point_code, is_pointer_code, is_binary_data
  11. from ezdxf.lldxf.types import GROUP_MARKERS, HEX_HANDLE_CODES, HANDLE_CODES, BINARY_FLAGS
  12. from ezdxf.tools import escape
  13. from ezdxf.sections.sections import KNOWN_SECTIONS
  14. from ezdxf.lldxf.packedtags import PackedTags
  15. from .reflinks import get_reference_link
  16. if TYPE_CHECKING: # import forward declarations
  17. from ezdxf.eztypes import Drawing, Table, DXFEntity, ExtendedTags, DXFTag
  18. # Tag groups
  19. # HTML templates
  20. # Section
  21. ALL_SECTIONS_TPL = '<div class="dxf-sections">\n{content}\n</div>'
  22. COMMON_SECTION_TPL = '<div id="{this_id}" class="dxf-section">' \
  23. '<div class="dxf-section-name">SECTION: {ref_link}</div>\n' \
  24. '<div class="button-bar">{prev} {next} <a class="link-button" href="#section-links">top<a/></div>\n' \
  25. '{{content}}\n</div>\n'
  26. HEADER_SECTION_TPL = '<div id="dxf-header" class="dxf-header">\n{content}\n</div>'
  27. TABLES_SECTION_TPL = '<div id="dxf-tables" class="dxf-tables">{content}</div>'
  28. BLOCKS_SECTION_TPL = '<div id="dxf-blocks" class="dxf-blocks">\n{content}\n</div>'
  29. # Section content
  30. HEADER_VAR_TPL = '<div class="hdr-var" ><span class="tag-code">{code}</span> <span class="var-type">{type}</span>' \
  31. ' <span class="tag-value">{value}</span></div>'
  32. CUSTOM_VAR_TPL = '<div class="hdr-var" >Custom Property: <span class="cu-tag">{tag}</span> ::' \
  33. ' <span class="cu-tag-value">{value}</span></div>'
  34. TABLE_TPL = '<div id="{name}-table" class="dxf-table">\n' \
  35. '<div class="dxf-table-name">{ref_link}</div>\n{nav}\n{header}\n{entries}\n</div>'
  36. ENTITIES_TPL = '<div class="dxf-entities">\n{}\n</div>'
  37. # DXF Entities
  38. ENTITY_TPL = '<div class="dxf-entity"><div class="dxf-entity-name">{name}</div>\n{references} {tags}\n</div>'
  39. BLOCK_TPL = '<div class="dxf-block">\n<div class="dxf-block-name">{name}</div>\n{block}\n{entities}\n{endblk}\n</div>'
  40. TAG_LIST_TPL = '<div class="dxf-tags">\n{content}\n</div>'
  41. # Basic Tags
  42. TAG_TPL = '<div class="dxf-tag" ><span class="tag-code">{code}</span> <span class="var-type">{type}</span>' \
  43. ' <span class="tag-value">{value}</span></div>'
  44. TAG_HANDLE_DEF_TPL = '<div class="dxf-tag"><span id="{value}" class="tag-code">{code}</span>' \
  45. ' <span class="var-type">{type}</span> <span class="tag-value">#{value}</span></div>'
  46. TAG_VALID_LINK_TPL = '<div class="dxf-tag"><span class="tag-code">{code}</span> <span class="var-type">{type}</span>' \
  47. ' <a class="tag-link" href="#{value}">#{value}</a></div>'
  48. TAG_INVALID_LINK_TPL = '<div class="dxf-tag"><span class="tag-code">{code}</span> <span class="var-type">{type}</span>' \
  49. ' <a class="tag-link" href="#{value}">#{value} [does not exist]</a></div>'
  50. MARKER_TPL = '<div class="tag-group-marker">{tag}</div>'
  51. CONTROL_TPL = '<div class="tag-ctrl-marker">{tag}</div>'
  52. # Links
  53. SECTION_LINKS_TPL = '<div class="button-bar">{buttons}</div>\n'
  54. REF_LINK_TPL = '<a class="dxf-ref-link" href={target} target="_blank" ' \
  55. 'title="Link to DXF-Reference provided by Autodesk®.">{name}</a>'
  56. BUTTON_BAR_TPL = '<div class="button-bar">{content}</div>'
  57. BUTTON_TPL = '<a class="link-button" href="#{target}">{name}</a>'
  58. MAX_STR_LEN = 100
  59. def build_ref_link_button(name: str) -> str:
  60. """Create a link-button for element *name* to the DXF reference.
  61. """
  62. link = get_reference_link(name)
  63. return REF_LINK_TPL.format(target=link, name=name)
  64. TAG_TYPES = {
  65. int: '<int>',
  66. float: '<float>',
  67. str: '<str>',
  68. }
  69. def tag_type_str(code: int) -> str:
  70. if code in GROUP_MARKERS:
  71. return '<ctrl>'
  72. elif code in HEX_HANDLE_CODES:
  73. return '<hex>'
  74. elif is_point_code(code):
  75. return '<point>'
  76. elif is_binary_data(code):
  77. return '<bin>'
  78. else:
  79. return TAG_TYPES[tag_type(code)]
  80. def with_bitmask(value: int) -> str:
  81. return "{0}, b{0:08b}".format(int(value))
  82. class DXF2HtmlConverter:
  83. def __init__(self, drawing: 'Drawing'):
  84. self.drawing = drawing
  85. self.entitydb = drawing.entitydb
  86. self.section_names_in_write_order = self._section_names_in_write_order()
  87. self.existing_pointers = self.collect_all_pointers()
  88. def _section_names_in_write_order(self) -> Sequence[str]:
  89. sections = self.drawing.sections
  90. write_order = list(name for name in KNOWN_SECTIONS if name in sections)
  91. write_order.extend(frozenset(sections.names()) - frozenset(KNOWN_SECTIONS))
  92. return write_order
  93. def dxf2html(self) -> str:
  94. """Creates a structured HTML view of the DXF tags - not a CAD drawing!
  95. """
  96. def get_name() -> str:
  97. if self.drawing.filename is None:
  98. return "unknown"
  99. else:
  100. filename = os.path.basename(self.drawing.filename)
  101. return os.path.splitext(filename)[0]
  102. template = load_resource('dxfpp.html')
  103. return template.format(
  104. name=get_name(),
  105. css=load_resource('dxfpp.css'),
  106. javascript=load_resource('dxfpp.js'),
  107. dxf_file=self.sections2html(),
  108. section_links=self.sections_link_bar(),
  109. )
  110. def sections2html(self) -> str:
  111. """Creates a <div> container of all DXF sections.
  112. """
  113. sections_html = []
  114. sections = self.drawing.sections
  115. for section_name in self.section_names_in_write_order:
  116. section = sections.get(section_name)
  117. if section is not None:
  118. section_template = self.create_section_html_template(section.name)
  119. sections_html.append(self.section2html(section, section_template))
  120. return ALL_SECTIONS_TPL.format(content="\n".join(sections_html))
  121. def create_section_html_template(self, name: str) -> str:
  122. """Creates a section template with buttons to the previous and next section.
  123. """
  124. def nav_targets() -> Tuple[str, str]:
  125. section_names = self.section_names_in_write_order
  126. index = section_names.index(name)
  127. prev_index = max(0, index - 1)
  128. succ_index = min(len(section_names) - 1, index + 1)
  129. return section_names[prev_index], section_names[succ_index]
  130. prev_id, next_id = nav_targets()
  131. prev_button = BUTTON_TPL.format(target=prev_id, name='previous')
  132. next_button = BUTTON_TPL.format(target=next_id, name='next')
  133. return COMMON_SECTION_TPL.format(
  134. ref_link=build_ref_link_button(name.upper()),
  135. this_id=name,
  136. prev=prev_button,
  137. next=next_button)
  138. def sections_link_bar(self) -> str:
  139. """Creates a <div> container as link bar to all DXF sections.
  140. """
  141. section_links = []
  142. for section_name in self.section_names_in_write_order:
  143. section_links.append(BUTTON_TPL.format(
  144. name=section_name.upper(),
  145. target=section_name
  146. ))
  147. return SECTION_LINKS_TPL.format(buttons=' \n'.join(section_links))
  148. def entities(self) -> Iterable['DXFEntity']:
  149. return iter(self.drawing.entities)
  150. def section2html(self, section, section_template: str) -> str:
  151. """Creates a <div> container of a specific DXF sections.
  152. """
  153. if section.name == 'HEADER':
  154. return section_template.format(content=self.hdrvars2html(section.hdrvars, section.custom_vars))
  155. elif section.name == 'ENTITIES':
  156. return section_template.format(content=self.entities2html(self.entities(), create_ref_links=True))
  157. elif section.name == 'CLASSES':
  158. return section_template.format(content=self.entities2html(section.classes.values()), create_ref_links=False,
  159. show_ref_status=False)
  160. elif section.name == 'OBJECTS':
  161. return section_template.format(content=self.entities2html(iter(section), create_ref_links=True,
  162. show_ref_status=True))
  163. elif section.name == 'TABLES':
  164. return section_template.format(content=self.tables2html(section)) # no iterator!
  165. elif section.name == 'BLOCKS':
  166. return section_template.format(content=self.blocks2html(iter(section)))
  167. else:
  168. return section_template.format(content=self.tags2html(section.tags()))
  169. @staticmethod
  170. def hdrvars2html(hdrvars, custom_vars) -> str:
  171. """DXF header section as <div> container.
  172. """
  173. def vartype(hdrvar) -> str:
  174. if is_point_code(hdrvar.code):
  175. dim = len(hdrvar.value) - 2
  176. return ("<point 2D>", "<point 3D>")[dim]
  177. else:
  178. return tag_type_str(hdrvar.code)
  179. varstrings = [
  180. HEADER_VAR_TPL.format(code=name, value=escape(str(hdrvar.value)), type=escape(vartype(hdrvar)))
  181. for name, hdrvar in hdrvars.items()
  182. ]
  183. custom_property_strings = [
  184. CUSTOM_VAR_TPL.format(tag=escape(str(tag)), value=escape(str(value)))
  185. for tag, value in custom_vars
  186. ]
  187. varstrings.extend(custom_property_strings)
  188. return HEADER_SECTION_TPL.format(content="\n".join(varstrings))
  189. def entities2html(self, entities: Iterable['DXFEntity'], create_ref_links=False, show_ref_status=False) -> str:
  190. """DXF entities as <div> container.
  191. """
  192. entity_strings = (self.entity2html(entity, create_ref_links, show_ref_status) for entity in entities)
  193. return ENTITIES_TPL.format("\n".join(entity_strings))
  194. def entity2html(self, entity: 'DXFEntity', create_ref_links=False, show_ref_status=False):
  195. """DXF entity as <div> container.
  196. """
  197. tags = entity.tags
  198. name = entity.dxftype()
  199. if create_ref_links: # use entity name as link to the DXF reference
  200. name = build_ref_link_button(name)
  201. refs = ""
  202. if show_ref_status:
  203. handle = tags.get_handle()
  204. if handle not in self.existing_pointers:
  205. refs = '<div class="ref-no">[unreferenced]</div>'
  206. else:
  207. refs = self.pointers2html(self.existing_pointers[handle])
  208. return ENTITY_TPL.format(name=name, tags=self.tags2html(tags), references=refs)
  209. def pointers2html(self, pointers: Iterable[str]) -> str:
  210. pointers_str = ", ".join(('<a class="tag-link" href="#{value}">{value}</a>'.format(value=ptr) for ptr in
  211. sorted(pointers, key=lambda x: int(x, 16))))
  212. return '<div class="ref-yes"> referenced by: {pointers}</div>'.format(pointers=pointers_str)
  213. def tags2html(self, tags: 'ExtendedTags') -> str:
  214. """DXF tag list as <div> container.
  215. """
  216. def tag2html(tag: 'DXFTag') -> str:
  217. def trim_str(vstr: str) -> str:
  218. if len(vstr) > MAX_STR_LEN:
  219. vstr = vstr[:(MAX_STR_LEN - 15)] + " ... " + vstr[-10:]
  220. return vstr
  221. if isinstance(tag, PackedTags):
  222. # inflate packed tags
  223. return ''.join(tag2html(tag) for tag in tag.dxftags())
  224. tpl = TAG_TPL
  225. if tag.code in HANDLE_CODES: # is handle definition
  226. tpl = TAG_HANDLE_DEF_TPL
  227. elif is_pointer_code(tag.code): # is handle link
  228. if tag.value in self.entitydb:
  229. tpl = TAG_VALID_LINK_TPL
  230. else:
  231. tpl = TAG_INVALID_LINK_TPL
  232. if tag.code in BINARY_FLAGS:
  233. vstr = with_bitmask(tag.value)
  234. else:
  235. if hasattr(tag, 'tostring'):
  236. s = tag.tostring()
  237. else:
  238. s = str(tag.value)
  239. vstr = trim_str(s)
  240. type_str = tag_type_str(tag.code)
  241. return tpl.format(code=tag.code, value=escape(vstr), type=escape(type_str))
  242. def group_marker(tag: 'DXFTag', tag_html: str) -> str:
  243. return tag_html if tag.code not in GROUP_MARKERS else MARKER_TPL.format(tag=tag_html)
  244. expanded_tags = self.expand_linked_tags(tags)
  245. tag_strings = (group_marker(tag, tag2html(tag)) for tag in expanded_tags)
  246. return TAG_LIST_TPL.format(content='\n'.join(tag_strings))
  247. def tables2html(self, tables: Iterable['Table']) -> str:
  248. """DXF tables section as <div> container.
  249. """
  250. navigation = self.create_table_navigation(tables)
  251. tables_html_strings = [self.table2html(table, navigation) for table in tables]
  252. return TABLES_SECTION_TPL.format(content='\n'.join(tables_html_strings))
  253. @staticmethod
  254. def create_table_navigation(table_section: Iterable['Table']) -> str:
  255. """Create a button bar with links to all DXF tables as <div> container.
  256. """
  257. buttons = []
  258. for table in table_section:
  259. name = table.name.upper()
  260. link = "{}-table".format(name)
  261. buttons.append(BUTTON_TPL.format(name=name, target=link))
  262. return BUTTON_BAR_TPL.format(content="\n".join(buttons))
  263. def table2html(self, table: 'Table', navigation='') -> str:
  264. """DXF table as <div> container.
  265. """
  266. header = ENTITY_TPL.format(name="TABLE HEADER", tags=self.tags2html(table._table_header), references="")
  267. entries = self.entities2html(table)
  268. table_name = table.name.upper()
  269. return TABLE_TPL.format(name=table_name, ref_link=build_ref_link_button(table_name), nav=navigation,
  270. header=header, entries=entries)
  271. def expand_linked_tags(self, tags: 'ExtendedTags') -> 'DXFTag':
  272. while True:
  273. yield from tags
  274. if not hasattr(tags, 'link') or tags.link is None:
  275. return
  276. tags = self.entitydb[tags.link]
  277. def collect_all_pointers(self) -> Dict[str, Set[str]]:
  278. existing_pointers = dict()
  279. for tags in self.entitydb.values():
  280. handle = tags.get_handle()
  281. for tag in self.expand_linked_tags(tags):
  282. if is_pointer_code(tag.code):
  283. pointers = existing_pointers.setdefault(tag.value, set())
  284. pointers.add(handle)
  285. return existing_pointers
  286. def blocks2html(self, blocks: Iterable) -> str:
  287. """DXF blocks section as <div> container.
  288. """
  289. block_strings = (self.block2html(block) for block in blocks)
  290. return BLOCKS_SECTION_TPL.format(content='\n'.join(block_strings))
  291. def block2html(self, block_layout) -> str:
  292. """DXF block entity as <div> container.
  293. """
  294. block_html = self.entity2html(block_layout.block, create_ref_links=True)
  295. if block_layout.name.upper() not in ('*MODEL_SPACE', '*PAPER_SPACE'):
  296. entities_html = self.entities2html(iter(block_layout), create_ref_links=True)
  297. else:
  298. entities_html = ''
  299. endblk_html = self.entity2html(block_layout.endblk, create_ref_links=True)
  300. return BLOCK_TPL.format(name=block_layout.name, block=block_html, entities=entities_html, endblk=endblk_html)
  301. def dxfpp(drawing: 'Drawing') -> str:
  302. """Creates a structured HTML view of the DXF tags - not a CAD drawing!
  303. """
  304. return DXF2HtmlConverter(drawing).dxf2html()
  305. def load_resource(filename: str) -> str:
  306. """Load external resource files.
  307. """
  308. src_path = os.path.dirname(__file__)
  309. src = os.path.join(src_path, filename)
  310. try:
  311. with open(src, mode="rt", encoding='utf-8') as fp:
  312. resource = fp.read()
  313. except IOError:
  314. errmsg = "IOError: can not read file '{}'.".format(src)
  315. print(errmsg)
  316. resource = errmsg
  317. return resource