File: custom_data.rst

package info (click to toggle)
ezdxf 1.4.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 104,528 kB
  • sloc: python: 182,341; makefile: 116; lisp: 20; ansic: 4
file content (520 lines) | stat: -rw-r--r-- 18,225 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
.. _tut_custom_data:

Storing Custom Data in DXF Files
================================

This tutorial describes how to store custom data in DXF files using
standard DXF features.

Saving data in comments is not covered in this section, because comments are not
a reliable way to store information in DXF files and `ezdxf` does not support
adding comments to DXF files. Comments are also ignored by `ezdxf` and many
other DXF libraries when loading DXF files, but there is a :mod:`ezdxf.comments`
module to load comments from DXF files.

The DXF data format is a very versatile and flexible data format and supports
various ways to store custom data. This starts by setting special header variables,
storing XData, AppData and extension dictionaries in DXF entities and objects,
storing XRecords in the OBJECTS section and ends by using proxy entities or
even extending the DXF format by user defined entities and objects.

This is the common prolog for all Python code examples shown in this tutorial:

.. code-block:: Python

    import ezdxf

    doc = ezdxf.new()
    msp = doc.modelspace()

Retrieving User Data
--------------------

Retrieving the custom data is a simple task by `ezdxf`, but often not possible in CAD
applications without using the scripting features (AutoLISP) or even the SDK.

AutoLISP Resources
++++++++++++++++++

- `Autodesk Developer Documentation <http://help.autodesk.com/view/OARX/2018/ENU/>`_
- `AfraLISP`_
- `Lee Mac Programming <http://www.lee-mac.com>`_

.. warning::

    I have no experience with AutoLISP so far and I created this scripts for
    AutoLISP while writing this tutorial. There may be better ways to accomplish
    these tasks, and feedback on this is very welcome.
    Everything is tested with BricsCAD and should also work with the
    full version of AutoCAD.

Header Section
--------------

The HEADER section has tow ways to store custom data.

Predefined User Variables
+++++++++++++++++++++++++

There are ten predefined user variables, five 16-bit integer variables called
``$USERI1`` up to ``$USERI5`` and five floating point variables (reals) called
``$USERR1`` up to ``$USERR5``.
This is very limited and the data maybe will be overwritten by the next
application which opens and saves the DXF file. Advantage of this methods is,
it works for all supported DXF versions starting at R12.

Settings the data:

.. literalinclude:: src/customdata/header.py
    :lines: 10-11

Getting the data by `ezdxf`:

.. literalinclude:: src/customdata/header.py
    :lines: 14-15

Getting the data in `BricsCAD` at the command line::

    : USERI1
    New current value for USERI1 (-32768 to 32767) <4711>:

Getting the data by AutoLISP::

    : (getvar 'USERI1)
    4711

Setting the value by AutoLISP::

    : (setvar 'USERI1 1234)
    1234

Custom Document Properties
++++++++++++++++++++++++++

This method defines custom document properties, but requires at least DXF R2004.
The custom document properties are stored in a :class:`~ezdxf.sections.header.CustomVars`
instance in the :attr:`~ezdxf.sections.header.HeaderSection.custom_vars` attribute of
the :class:`~ezdxf.sections.header.HeaderSection` object and supports only
string values.

Settings the data:

.. literalinclude:: src/customdata/header.py
    :lines: 18

Getting the data by `ezdxf`:

.. literalinclude:: src/customdata/header.py
    :lines: 21

The document property ``MyFirstVar`` is available in `BricsCAD` as FIELD
variable:

.. image:: gfx/custom_header_property.png

AutoLISP script for getting the custom document properties:

.. code-block:: Lisp

    (defun C:CUSTOMDOCPROPS (/ Info Num Index Custom)
      (vl-load-com)
      (setq acadObject (vlax-get-acad-object))
      (setq acadDocument (vla-get-ActiveDocument acadObject))

      ;;Get the SummaryInfo
      (setq Info (vlax-get-Property acadDocument 'SummaryInfo))
      (setq Num (vla-NumCustomInfo Info))
      (setq Index 0)
      (repeat Num
        (vla-getCustomByIndex Info Index 'ID 'Value)
        (setq Custom (cons (cons ID Value) Custom))
        (setq Index (1+ Index))
      )  ;repeat

      (if Custom (reverse Custom))
    )

Running the script in BricsCAD:

.. code-block:: Text

    : (load "customdocprops.lsp")
    C:CUSTOMDOCPROPS
    : CUSTOMDOCPROPS
    (("MyFirstVar" . "First Value"))

Meta Data
---------

Starting with version v0.16.4 `ezdxf` stores some meta data in the DXF file and
the AppID ``EZDXF`` will be created.
Two entries will be added to the :class:`~ezdxf.document.MetaData`
instance, the ``CREATED_BY_EZDXF`` for DXF documents created by `ezdxf` and the
entry ``WRITTEN_BY_EZDXF`` if the DXF document will be saved by `ezdxf`.
The marker string looks like this ``"0.17b0 @ 2021-09-18T05:14:37.921826+00:00"``
and contains the `ezdxf` version and an UTC timestamp in ISO format.

You can add your own data to the :class:`~ezdxf.document.MetaData`
instance as a string with a maximum of 254 characters and choose a good name
which may never be used by `ezdxf` in the future.

.. code-block:: Python

    metadata = doc.ezdxf_metadata()
    metadata["MY_UNIQUE_KEY"] = "my additional meta data"

    print(metadata.get("CREATED_BY_EZDXF"))
    print(metadata.get("MY_UNIQUE_KEY"))


The data is stored as XDATA in then BLOCK entity of the model space for DXF R12
and for DXF R2000 and later as a DXF :class:`~ezdxf.entities.Dictionary`
in the root dictionary by the key ``EZDXF_META``.
See following chapters for accessing such data by AutoLISP.

XDATA
-----

:ref:`extended_data` is a way to attach arbitrary data to DXF entities.
Each application needs a unique AppID registered in the AppID table to add
XDATA to an entity. The AppID ``ACAD`` is reserved and by using `ezdxf`
the AppID ``EZDXF`` is also registered automatically.
The total size of XDATA for a single DXF entity is limited to 16kB for AutoCAD.
XDATA is supported by all DXF versions and is accessible by AutoLISP.

The valid group codes for extended data are limited to the following values,
see also the internals of :ref:`xdata_internals`:

================= ==============================================================
Group Code        Description
================= ==============================================================
1000              Strings up to 255 bytes long
1001              (fixed) Registered application name up to 31 bytes long
1002              (fixed) An extended data control string ``'{'``  or ``'}'``
1004              Binary data
1005              Database Handle of entities in the drawing database
1010              3D point, in the order X, Y, Z that will not be modified at
                  any transformation of the entity
1011              A WCS point that is moved, scaled, rotated and mirrored
                  along with the entity
1012              A WCS displacement that is scaled, rotated and
                  mirrored along with the entity, but not moved
1013              A WCS direction that is rotated and mirrored along
                  with the entity but not moved and scaled.
1040              A real value
1041              Distance, a real value that is scaled along with the entity
1042              Scale Factor, a real value that is scaled along with the entity
1070              A 16-bit integer (signed or unsigned)
1071              A 32-bit signed (long) integer
================= ==============================================================

Group codes are not unique in the XDATA section and can be repeated, therefore
tag order matters.

.. literalinclude:: src/customdata/xdata.py
    :lines: 10-39

AutoLISP script for getting XDATA for AppID ``YOUR_UNIQUE_ID``:

.. code-block:: Lisp

    (defun C:SHOWXDATA (/ entity_list xdata_list)
        (setq entity_list (entget (car (entsel)) '("YOUR_UNIQUE_ID")))
        (setq xdata_list (assoc -3 entity_list))
        (car (cdr xdata_list))
    )

Script output:

.. code-block:: Text

    : SHOWXDATA
    Select entity: ("YOUR_UNIQUE_ID" (1000 . "custom text") (1040 . 3.141592) ...

.. seealso::

    - `AfraLISP XDATA tutorial <https://www.afralisp.net/autolisp/tutorials/extended-entity-data-part-1.php>`_
    - :ref:`extended_data` Reference

XDATA Helper Classes
--------------------

The :class:`~ezdxf.entities.xdata.XDataUserList` and
:class:`~ezdxf.entities.xdata.XDataUserDict` are helper classes to manage XDATA
content in a simple way.

Both classes store the Python types ``int``, ``float`` and ``str`` and the
`ezdxf` type :class:`~ezdxf.math.Vec3`. As the names suggests has the
:class:`XDataUserList` a list-like interface and the :class:`XDataUserDict` a
dict-like interface. This classes can not contain additional container types,
but multiple lists and/or dicts can be stored in the same XDATA section for the
same AppID.

These helper classes uses a fixed group code for each data type:

==== ========================
1001 strings (max. 255 chars)
1040 floats
1071 32-bit ints
1010 Vec3
==== ========================

Additional required imports for these examples:

.. code-block:: Python

    from ezdxf.math import Vec3
    from ezdxf.entities.xdata import XDataUserDict, XDataUserList

Example for :class:`~ezdxf.entities.xdata.XDataUserDict`:

Each :class:`XDataUserDict` has a unique name, the default name is "DefaultDict"
and the default AppID is ``EZDXF``.
If you use your own AppID, don't forget to create the requited AppID table entry
like :code:`doc.appids.new("MyAppID")`, otherwise AutoCAD will not open the
DXF file.

.. literalinclude:: src/customdata/xdata_helper.py
    :lines: 11-19

If you modify the content of without using the context manager
:meth:`~ezdxf.entities.xdata.XDataUserDict.entity`, you have to call
:meth:`~ezdxf.entities.xdata.XDataUserDict.commit` by yourself, to transfer the
modified data back into the XDATA section.

Getting the data back from an entity:

.. literalinclude:: src/customdata/xdata_helper.py
    :lines: 22-25

Example for :class:`~ezdxf.entities.xdata.XDataUserList`:

This example stores the data in a :class:`XDataUserList` named "AppendedPoints",
the default name is "DefaultList" and the default AppID is ``EZDXF``.

.. literalinclude:: src/customdata/xdata_helper.py
    :lines: 29-32

Now the content of both classes are stored in the same XDATA section for AppID
``EZDXF``. The :class:`XDataUserDict` is stored by the name "DefaultDict" and
the :class:`XDataUserList` is stored by the name "AppendedPoints".

Getting the data back from an entity:

.. literalinclude:: src/customdata/xdata_helper.py
    :lines: 35-39

.. seealso::

    - :class:`~ezdxf.entities.xdata.XDataUserList` class
    - :class:`~ezdxf.entities.xdata.XDataUserDict` class

Extension Dictionaries
----------------------

Extension dictionaries are another way to attach custom data to any DXF
entity. This method requires DXF R13/14 or later. I will use the short term
XDICT for extension dictionaries in this tutorial.

The :ref:`extension_dictionary` is a regular DXF :class:`~ezdxf.entities.Dictionary`
which can store (key, value) pairs where the key is a string and the value is a
DXF object from the OBJECTS section.
The usual objects to store custom data are :class:`~ezdxf.entities.DictionaryVar`
to store simple strings and :class:`~ezdxf.entities.XRecord` to store complex
data.

Unlike XDATA, custom data attached by extension dictionary will not be
transformed along with the DXF entity!

This example shows how to manage the XDICT and to store simple strings as
:class:`~ezdxf.entities.DictionaryVar` objects in the XDICT, to store more
complex data go to the next section `XRecord`_.

1. Get or create the XDICT for an entity:

.. literalinclude:: src/customdata/xdict.py
    :lines: 10-18

2. Add strings as :class:`~ezdxf.entities.DictionaryVar` objects to the XDICT.
No AppIDs required, but existing keys will be overridden, so be careful by
choosing your keys:

.. literalinclude:: src/customdata/xdict.py
    :lines: 20-21

3. Retrieve the strings from the XDICT as :class:`~ezdxf.entities.DictionaryVar`
objects:

.. literalinclude:: src/customdata/xdict.py
    :lines: 23-24


The AutoLISP access to DICTIONARIES is possible, but it gets complex and I'm
only referring to the `AfraLISP Dictionaries and XRecords`_ tutorial.

.. seealso::

    - `AfraLISP Dictionaries and XRecords`_ Tutorial
    - :ref:`extension_dictionary` Reference
    - DXF :class:`~ezdxf.entities.Dictionary` Reference
    - :class:`~ezdxf.entities.DictionaryVar` Reference

XRecord
-------

The :class:`~ezdxf.entities.XRecord` object can store arbitrary data like the
XDATA section, but is not limited by size and can use all group codes in the
range from 1 to 369 for :ref:`dxf_tags_internals`.
The :class:`~ezdxf.entities.XRecord` can be referenced by any DXF
:class:`~ezdxf.entities.Dictionary`, other :class:`XRecord` objects (tricky
ownership!), the XDATA section (store handle by group code 1005) or any other
DXF object by adding the :class:`XRecord` object to the
:ref:`extension_dictionary` of the DXF entity.

It is recommend to follow the DXF reference to assign appropriate group codes
to :ref:`dxf_tags_internals`. My recommendation is shown in the table
below, but all group codes from 1 to 369 are valid. I advice against using the
group codes 100 and 102 (structure tags) to avoid confusing generic tag loaders.
Unfortunately, Autodesk doesn't like general rules and uses DXF format
exceptions everywhere.

=== ======================
1   strings (max. 2049 chars)
2   structure tags as strings like ``"{"`` and  ``"}"``
10  points and vectors
40  floats
90  integers
330 handles
=== ======================

Group codes are not unique in :class:`XRecord` and can be repeated, therefore
tag order matters.

This example shows how to attach a :class:`~ezdxf.entities.XRecord` object to a
LINE entity by :ref:`extension_dictionary`:

.. literalinclude:: src/customdata/xrecord.py
    :lines: 11-28

Script output:

.. code-block:: Text

    [DXFTag(1, 'text1'),
     DXFTag(40, 3.141592),
     DXFTag(90, 256),
     DXFVertex(10, (1.0, 2.0, 0.0)),
     DXFTag(330, '30')]

Unlike XDATA, custom data attached by extension dictionary will not be
transformed along with the DXF entity! To react to entity modifications by a
CAD applications it is possible to write event handlers by AutoLISP, see the
`AfraLISP Reactors Tutorial`_ for more information. This is very advanced stuff!

.. seealso::

    - `AfraLISP Dictionaries and XRecords`_ Tutorial
    - `AfraLISP Reactors Tutorial`_
    - :class:`~ezdxf.entities.XRecord` Reference
    - helper functions: :func:`ezdxf.lldxf.types.dxftag` and :func:`ezdxf.lldxf.types.tuples_to_tags`

XRecord Helper Classes
----------------------

The :class:`~ezdxf.urecord.UserRecord` and :class:`~ezdxf.urecord.BinaryRecord`
are helper classes to manage XRECORD content in a simple way.
The :class:`~ezdxf.urecord.UserRecord` manages the data as plain
Python types: ``dict``, ``list``, ``int``, ``float``, ``str`` and the `ezdxf`
types :class:`~ezdxf.math.Vec2` and :class:`~ezdxf.math.Vec3`. The top level
type for the :attr:`UserRecord.data` attribute has to be a ``list``.
The :class:`~ezdxf.urecord.BinaryRecord` stores arbitrary binary data as `BLOB`_.
These helper classes uses fixed group codes to manage the data in XRECORD,
you have no choice to change them.

Additional required imports for these examples:

.. literalinclude:: src/customdata/urecord.py
    :lines: 6-11

Example 1: Store entity specific data in the :ref:`extension_dictionary`:


.. literalinclude:: src/customdata/urecord.py
    :lines: 23-37

Example 1: Get entity specific data back from the :ref:`extension_dictionary`:

.. literalinclude:: src/customdata/urecord.py
    :lines: 42-47

If you modify the content of :attr:`UserRecord.data` without using the context
manager, you have to call :meth:`~ezdxf.urecord.UserRecord.commit` by yourself,
to store the modified data back into the XRECORD.

Example 2: Store arbitrary data in DICTIONARY objects.
The XRECORD is stored in the named DICTIONARY, called :attr:`rootdict` in `ezdxf`.
This DICTIONARY is the root entity for the tree-like data structure
stored in the OBJECTS section, see also the documentation of the
:mod:`ezdxf.sections.objects` module.

.. literalinclude:: src/customdata/urecord.py
    :lines: 52-69

Example 2:  Get user data back from the DICTIONARY object

.. literalinclude:: src/customdata/urecord.py
    :lines: 74-78

Example 3: Store arbitrary binary data

.. literalinclude:: src/customdata/urecord.py
    :lines: 83-91

Example 3: Get binary data back from the DICTIONARY object

.. literalinclude:: src/customdata/urecord.py
    :lines: 99-106

.. hint::

    Don't be fooled, the ability to save any binary data such as images, office
    documents, etc. in the DXF file doesn't impress AutoCAD, it simply ignores
    this data, this data only has a meaning for your application!

.. seealso::

    - :mod:`~ezdxf.urecord` module
    - :class:`~ezdxf.urecord.UserRecord` class
    - :class:`~ezdxf.urecord.BinaryRecord` class


AppData
-------

:ref:`application_defined_data` was introduced in DXF R13/14 and is used by
AutoCAD internally to store the handle to the :ref:`extension_dictionary` and
the :ref:`reactors` in DXF entities.
`Ezdxf` supports these kind of data storage for any AppID and the data is
preserved by AutoCAD and BricsCAD, but I haven't found a way to access this
data by AutoLISP or even the SDK.
So I don't recommend this feature to store application defined data,
because :ref:`extended_data` and the :ref:`extension_dictionary` are well
documented and safe ways to attach custom data to entities.

.. literalinclude:: src/customdata/appdata.py
    :lines: 10-25

Printed output:

.. code-block:: Text

    LINE(#30) has 3 tags of AppData for AppID 'YOUR_UNIQUE_ID'
    (300, 'custom text')
    (370, 4711)
    (460, 3.141592)

.. _AfraLISP: https://www.afralisp.net/index.php
.. _AfraLISP Dictionaries and XRecords: https://www.afralisp.net/autolisp/tutorials/dictionaries-and-xrecords.php
.. _Visual AutoLISP: https://www.afralisp.net/visual-lisp/
.. _AfraLISP Reactors Tutorial: https://www.afralisp.net/visual-lisp/tutorials/reactors-part-1.php
.. _BLOB: https://en.wikipedia.org/wiki/Binary_large_object