dxfdict.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. # Created: 22.03.2011
  2. # Copyright (c) 2011-2018, Manfred Moitzi
  3. # License: MIT-License
  4. from typing import TYPE_CHECKING, cast, KeysView, ItemsView, Any, Union
  5. from ezdxf.lldxf.const import DXFKeyError
  6. from ezdxf.lldxf.packedtags import TagDict
  7. from ezdxf.lldxf import loader
  8. from .dxfobjects import DXFObject, DefSubclass, DXFAttributes, DXFAttr, ExtendedTags
  9. from .dxfobjects import none_subclass
  10. if TYPE_CHECKING:
  11. from collections import OrderedDict
  12. from ezdxf.eztypes import Tags, DXFEntity, Auditor
  13. @loader.register('ACDBDICTIONARYWDFLT', legacy=False)
  14. @loader.register('DICTIONARY', legacy=False)
  15. def tag_processor(tags: ExtendedTags) -> ExtendedTags:
  16. subclass = tags.get_subclass('AcDbDictionary')
  17. d = TagDict.from_tags(subclass)
  18. d.replace_tags(subclass)
  19. return tags
  20. _DICT_TPL = """0
  21. DICTIONARY
  22. 5
  23. 0
  24. 330
  25. 0
  26. 100
  27. AcDbDictionary
  28. 281
  29. 1
  30. """
  31. dictionary_subclass = DefSubclass('AcDbDictionary', {
  32. 'hard_owned': DXFAttr(280, default=0), # Hard-owner flag.
  33. # If set to 1, indicates that elements of the dictionary are to be treated as hard-owned
  34. 'cloning': DXFAttr(281, default=1), # Duplicate record cloning flag (determines how to merge duplicate entries):
  35. # 0 = not applicable
  36. # 1 = keep existing
  37. # 2 = use clone
  38. # 3 = <xref>$0$<name>
  39. # 4 = $0$<name>
  40. # 5 = Unmangle name
  41. # 3: entry name
  42. # 350: entry handle, some DICTIONARY objects have 360 as handle group code, this is accepted by AutoCAD but not
  43. # documented by the DXF reference!!! ezdxf replaces 360 codes by 350.
  44. })
  45. class DXFDictionary(DXFObject):
  46. __slots__ = ('_cached_dict',)
  47. """
  48. AutoCAD maintains items such as mline styles and group definitions as objects in dictionaries.
  49. Other applications are free to create and use their own dictionaries as they see fit. The prefix "ACAD_" is reserved
  50. for use by AutoCAD applications.
  51. DXFDictionary entries are (key, handle) values, so it can only store handles and nothing else, to store other
  52. values, you have to create a DXFDictionaryVar object, and store its handle.
  53. """
  54. TEMPLATE = tag_processor(ExtendedTags.from_text(_DICT_TPL))
  55. DXFATTRIBS = DXFAttributes(
  56. none_subclass,
  57. dictionary_subclass,
  58. )
  59. @property
  60. def AcDbDictinary(self) -> 'Tags':
  61. return self.tags.subclasses[1]
  62. @property
  63. def is_hard_owner(self) -> bool:
  64. return bool(self.get_dxf_attrib('hard_owned', False))
  65. @property
  66. def data(self) -> 'OrderedDict':
  67. try:
  68. return self._cached_dict
  69. except AttributeError:
  70. self._cached_dict = cast('OrderedDict', self.AcDbDictinary.get_first_value(TagDict.code))
  71. return self._cached_dict
  72. def keys(self) -> KeysView:
  73. """
  74. Generator for the dictionary's keys.
  75. """
  76. return self.data.keys()
  77. def items(self) -> ItemsView:
  78. """
  79. Generator for the dictionary's items as (key, value) pairs.
  80. """
  81. return self.data.items()
  82. def __getitem__(self, key: str) -> str:
  83. """
  84. Return the value for `key` if key is in the dictionary, else raises a `KeyError`.
  85. """
  86. return self.get(key)
  87. def __setitem__(self, key: str, value: str) -> None:
  88. """
  89. Add item `(key, value)` to dictionary.
  90. """
  91. return self.add(key, value)
  92. def __delitem__(self, key: str) -> None:
  93. """
  94. Remove element `key` from the dictionary. Raises `KeyError` if key is not contained in the dictionary.
  95. """
  96. return self.remove(key)
  97. def __contains__(self, key: str) -> bool:
  98. """
  99. Return True if the dictionary has `key`, else False.
  100. """
  101. return key in self.data
  102. def __len__(self) -> int:
  103. """
  104. Return the number of items in the dictionary.
  105. """
  106. return len(self.data)
  107. count = __len__
  108. def get(self, key: str, default: Any = DXFKeyError) -> str:
  109. """
  110. Return the value (handle) for `key` if `key` is in the dictionary, else `default` or raises a `DXFKeyError`
  111. for `default`=`DXFKeyError`.
  112. """
  113. try:
  114. return self.data[key]
  115. except KeyError:
  116. if default is DXFKeyError:
  117. raise DXFKeyError("KeyError: '{}'".format(key))
  118. else:
  119. return default
  120. get_handle = get # synonym
  121. def get_entity(self, key: str) -> Union['DXFEntity', str]:
  122. """
  123. Get object referenced by handle associated by `key` as wrapped entity, raises a `KeyError` if *key* not exists.
  124. """
  125. handle = self.get(key)
  126. if self.drawing is not None:
  127. return self.dxffactory.wrap_handle(handle)
  128. else:
  129. return handle
  130. def add(self, key: str, value: str) -> None:
  131. """
  132. Add item `(key, value)` to dictionary.
  133. """
  134. self.data[key] = value
  135. def remove(self, key: str) -> None:
  136. """
  137. Remove element `key` from the dictionary. Raises `DXFKeyError` if `key` not exists. Deletes hard owned DXF
  138. objects from OBJECTS section.
  139. """
  140. data = self.data
  141. if key not in data:
  142. raise DXFKeyError(key)
  143. if self.is_hard_owner:
  144. entity = self.get_entity(key)
  145. # Presumption: hard owned DXF objects always reside in the OBJECTS section
  146. self.drawing.objects.delete_entity(entity)
  147. del data[key]
  148. def discard(self, key: str) -> None:
  149. """
  150. Remove `key` from dictionary, if exists. Does NOT delete hard owned entities!
  151. """
  152. try:
  153. del self.data[key]
  154. except KeyError:
  155. pass
  156. def clear(self) -> None:
  157. """
  158. Removes all entries from DXFDictionary, and also deletes all hard owned DXF objects from OBJECTS section.
  159. """
  160. if self.is_hard_owner:
  161. self.delete_hard_owned_entries()
  162. self.data.clear()
  163. def delete_hard_owned_entries(self) -> None:
  164. # Presumption: hard owned DXF objects always reside in the OBJECTS section
  165. objects = self.drawing.objects
  166. wrap = self.dxffactory.wrap_handle
  167. for key, handle in self.items():
  168. objects.delete_entity(wrap(handle))
  169. def add_new_dict(self, key: str) -> 'DXFDictionary':
  170. """
  171. Create a new sub dictionary.
  172. Args:
  173. key: name of the sub dictionary
  174. """
  175. dxf_dict = self.drawing.objects.add_dictionary(owner=self.dxf.handle)
  176. self.add(key, dxf_dict.dxf.handle)
  177. return dxf_dict
  178. def get_required_dict(self, key: str) -> 'DXFDictionary':
  179. """
  180. Get DXFDictionary `key`, if exists or create a new DXFDictionary.
  181. """
  182. try:
  183. dxf_dict = self.get_entity(key)
  184. except DXFKeyError:
  185. dxf_dict = self.add_new_dict(key)
  186. return dxf_dict
  187. def audit(self, auditor: 'Auditor') -> None:
  188. auditor.check_handles_exists(self, handles=self.data.values())
  189. def destroy(self) -> None:
  190. if self.get_dxf_attrib('hard_owned', False):
  191. self.delete_hard_owned_entries()
  192. _DICT_WITH_DEFAULT_CLS = """0
  193. CLASS
  194. 1
  195. ACDBDICTIONARYWDFLT
  196. 2
  197. AcDbDictionaryWithDefault
  198. 3
  199. ObjectDBX Classes
  200. 90
  201. 0
  202. 91
  203. 0
  204. 280
  205. 0
  206. 281
  207. 0
  208. """
  209. _DICT_WITH_DEFAULT_TPL = """0
  210. ACDBDICTIONARYWDFLT
  211. 5
  212. 0
  213. 330
  214. 0
  215. 100
  216. AcDbDictionary
  217. 281
  218. 1
  219. 100
  220. AcDbDictionaryWithDefault
  221. 340
  222. 0
  223. """
  224. class DXFDictionaryWithDefault(DXFDictionary):
  225. TEMPLATE = tag_processor(ExtendedTags.from_text(_DICT_WITH_DEFAULT_TPL))
  226. CLASS = (ExtendedTags.from_text(_DICT_WITH_DEFAULT_CLS))
  227. DXFATTRIBS = DXFAttributes(
  228. none_subclass,
  229. dictionary_subclass,
  230. DefSubclass('AcDbDictionaryWithDefault', {
  231. 'default': DXFAttr(340),
  232. }),
  233. )
  234. def get(self, key: str, default: Any = DXFKeyError) -> str:
  235. """
  236. Return the value for `key` if exists else returns the predefined dictionary wide `default` value. Parameter
  237. `default` is always ignored!
  238. """
  239. return super(DXFDictionaryWithDefault, self).get(key, default=self.dxf.default)
  240. _DICTIONARYVAR_CLS = """0
  241. CLASS
  242. 1
  243. DICTIONARYVAR
  244. 2
  245. AcDbDictionaryVar
  246. 3
  247. ObjectDBX Classes
  248. 90
  249. 0
  250. 91
  251. 0
  252. 280
  253. 0
  254. 281
  255. 0
  256. """
  257. _DICTIONARYVAR_TPL = """0
  258. DICTIONARYVAR
  259. 5
  260. 0
  261. 330
  262. 0
  263. 102
  264. DictionaryVariables
  265. 280
  266. 0
  267. 1
  268. """
  269. class DXFDictionaryVar(DXFObject):
  270. """
  271. DICTIONARYVAR objects are used by AutoCAD as a means to store named values in the database for setvar / getvar
  272. purposes without the need to add entries to the DXF HEADER section. System variables that are stored as
  273. DICTIONARYVAR objects are the following:
  274. - DEFAULTVIEWCATEGORY
  275. - DIMADEC
  276. - DIMASSOC
  277. - DIMDSEP
  278. - DRAWORDERCTL
  279. - FIELDEVAL
  280. - HALOGAP
  281. - HIDETEXT
  282. - INDEXCTL
  283. - INDEXCTL
  284. - INTERSECTIONCOLOR
  285. - INTERSECTIONDISPLAY
  286. - MSOLESCALE
  287. - OBSCOLOR
  288. - OBSLTYPE
  289. - OLEFRAME
  290. - PROJECTNAME
  291. - SORTENTS
  292. - UPDATETHUMBNAIL
  293. - XCLIPFRAME
  294. - XCLIPFRAME
  295. """
  296. TEMPLATE = ExtendedTags.from_text(_DICTIONARYVAR_TPL)
  297. CLASS = ExtendedTags.from_text(_DICTIONARYVAR_CLS)
  298. DXFATTRIBS = DXFAttributes(
  299. none_subclass,
  300. DefSubclass('DictionaryVariables', {
  301. 'schema': DXFAttr(280, default=0), # Object schema number (currently set to 0)
  302. 'value': DXFAttr(1),
  303. }),
  304. )