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 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:
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¶
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:
doc.header["$USERI1"] = 4711
doc.header["$USERR1"] = 3.141592
Getting the data by ezdxf:
i1 = doc.header["$USERI1"]
r1 = doc.header["$USERR1"]
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 CustomVars
instance in the custom_vars
attribute of
the HeaderSection
object and supports only
string values.
Settings the data:
doc.header.custom_vars.append("MyFirstVar", "First Value")
Getting the data by ezdxf:
my_first_var = doc.header.custom_vars.get("MyFirstVar", "Default Value")
The document property MyFirstVar
is available in BricsCAD as FIELD
variable:
AutoLISP script for getting the custom document properties:
(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:
: (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 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 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.
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 Dictionary
in the root dictionary by the key EZDXF_META
.
See following chapters for accessing such data by AutoLISP.
XDATA¶
Extended Data (XDATA) 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 Extended Data:
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 |
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.
# register your appid
APPID = "YOUR_UNIQUE_ID"
doc.appids.add(APPID)
# create a DXF entity
line = msp.add_line((0, 0), (1, 0))
# setting the data
line.set_xdata(APPID, [
# basic types
(1000, "custom text"),
(1040, 3.141592),
(1070, 4711), # 16bit
(1071, 1_048_576), # 32bit
# points and vectors
(1010, (10, 20, 30)),
(1011, (11, 21, 31)),
(1012, (12, 22, 32)),
(1013, (13, 23, 33)),
# scaled distances and factors
(1041, 10),
(1042, 10),
])
# getting the data
if line.has_xdata(APPID):
tags = line.get_xdata(APPID)
print(f"{str(line)} has {len(tags)} tags of XDATA for AppID {APPID!r}")
for tag in tags:
print(tag)
AutoLISP script for getting XDATA for AppID YOUR_UNIQUE_ID
:
(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:
: SHOWXDATA
Select entity: ("YOUR_UNIQUE_ID" (1000 . "custom text") (1040 . 3.141592) ...
See also
Extended Data (XDATA) Reference
XDATA Helper Classes¶
The XDataUserList
and
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 Vec3
. As the names suggests has the
XDataUserList
a list-like interface and the 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:
from ezdxf.math import Vec3
from ezdxf.entities.xdata import XDataUserDict, XDataUserList
Example for XDataUserDict
:
Each 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 doc.appids.new("MyAppID")
, otherwise AutoCAD will not open the
DXF file.
doc = ezdxf.new()
msp = doc.modelspace()
line = msp.add_line((0, 0), (1, 0))
with XDataUserDict.entity(line) as user_dict:
user_dict["CreatedBy"] = "mozman"
user_dict["Float"] = 3.1415
user_dict["Int"] = 4711
user_dict["Point"] = Vec3(1, 2, 3)
If you modify the content of without using the context manager
entity()
, you have to call
commit()
by yourself, to transfer the
modified data back into the XDATA section.
Getting the data back from an entity:
with XDataUserDict.entity(line) as user_dict:
print(user_dict)
# acts like any other dict()
storage = dict(user_dict)
Example for XDataUserList
:
This example stores the data in a XDataUserList
named “AppendedPoints”,
the default name is “DefaultList” and the default AppID is EZDXF
.
with XDataUserList.entity(line, name="AppendedPoints") as user_list:
user_list.append(Vec3(1, 0, 0))
user_list.append(Vec3(0, 1, 0))
user_list.append(Vec3(0, 0, 1))
Now the content of both classes are stored in the same XDATA section for AppID
EZDXF
. The XDataUserDict
is stored by the name “DefaultDict” and
the XDataUserList
is stored by the name “AppendedPoints”.
Getting the data back from an entity:
with XDataUserList.entity(line, name="AppendedPoints") as user_list:
print(user_list)
storage = list(user_list)
print(f"Copy of XDataUserList: {storage}")
See also
XDataUserList
classXDataUserDict
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 Extension Dictionary is a regular DXF 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 DictionaryVar
to store simple strings and 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
DictionaryVar
objects in the XDICT, to store more
complex data go to the next section XRecord.
Get or create the XDICT for an entity:
# create a DXF entity
line = msp.add_line((0, 0), (1, 0))
if line.has_extension_dict:
# get the extension dictionary
xdict = line.get_extension_dict()
else:
# create a new extension dictionary
xdict = line.new_extension_dict()
2. Add strings as DictionaryVar
objects to the XDICT.
No AppIDs required, but existing keys will be overridden, so be careful by
choosing your keys:
xdict.add_dictionary_var("DATA1", "Your custom data string 1")
xdict.add_dictionary_var("DATA2", "Your custom data string 2")
3. Retrieve the strings from the XDICT as DictionaryVar
objects:
print(f"DATA1 is '{xdict['DATA1'].value}'")
print(f"DATA2 is '{xdict['DATA2'].value}'")
The AutoLISP access to DICTIONARIES is possible, but it gets complex and I’m only referring to the AfraLISP Dictionaries and XRecords tutorial.
See also
AfraLISP Dictionaries and XRecords Tutorial
Extension Dictionary Reference
DXF
Dictionary
ReferenceDictionaryVar
Reference
XRecord¶
The 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 DXF Tags.
The XRecord
can be referenced by any DXF
Dictionary
, other XRecord
objects (tricky
ownership!), the XDATA section (store handle by group code 1005) or any other
DXF object by adding the XRecord
object to the
Extension Dictionary of the DXF entity.
It is recommend to follow the DXF reference to assign appropriate group codes to DXF Tags. 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 |
10 |
points and vectors |
40 |
floats |
90 |
integers |
330 |
handles |
Group codes are not unique in XRecord
and can be repeated, therefore
tag order matters.
This example shows how to attach a XRecord
object to a
LINE entity by Extension Dictionary:
line = msp.add_line((0, 0), (1, 0))
line2 = msp.add_line((0, 2), (1, 2))
if line.has_extension_dict:
xdict = line.get_extension_dict()
else:
xdict = line.new_extension_dict()
xrecord = xdict.add_xrecord("DATA1")
xrecord.reset([
(1, "text1"), # string
(40, 3.141592), # float
(90, 256), # 32-bit int
(10, (1, 2, 0)), # points and vectors
(330, line2.dxf.handle) # handles
])
print(xrecord.tags)
Script output:
[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!
See also
AfraLISP Dictionaries and XRecords Tutorial
XRecord
Referencehelper functions:
ezdxf.lldxf.types.dxftag()
andezdxf.lldxf.types.tuples_to_tags()
XRecord Helper Classes¶
The UserRecord
and BinaryRecord
are helper classes to manage XRECORD content in a simple way.
The UserRecord
manages the data as plain
Python types: dict
, list
, int
, float
, str
and the ezdxf
types Vec2
and Vec3
. The top level
type for the UserRecord.data
attribute has to be a list
.
The 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:
from pprint import pprint
import ezdxf
from ezdxf.math import Vec3
from ezdxf.urecord import UserRecord, BinaryRecord
from ezdxf.entities import XRecord
import zlib
Example 1: Store entity specific data in the Extension Dictionary:
line = msp.add_line((0, 0), (1, 0))
xdict = line.new_extension_dict()
xrecord = xdict.add_xrecord("MyData")
with UserRecord(xrecord) as user_record:
user_record.data = [ # top level has to be a list!
"MyString",
4711,
3.1415,
Vec3(1, 2, 3),
{
"MyIntList": [1, 2, 3],
"MyFloatList": [4.5, 5.6, 7.8],
},
]
Example 1: Get entity specific data back from the Extension Dictionary:
if line.has_extension_dict:
xdict = line.get_extension_dict()
xrecord = xdict.get("MyData")
if isinstance(xrecord, XRecord):
user_record = UserRecord(xrecord)
pprint(user_record.data)
If you modify the content of UserRecord.data
without using the context
manager, you have to call 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 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
ezdxf.sections.objects
module.
# Get the existing DICTIONARY object or create a new DICTIONARY object:
my_dict = doc.objects.rootdict.get_required_dict("MyDict")
# Create a new XRECORD object, the DICTIONARY object is the owner of this
# new XRECORD:
xrecord = my_dict.add_xrecord("MyData")
# This example creates the user record without the context manager.
user_record = UserRecord(xrecord)
# Store user data:
user_record.data = [
"Just another user record",
4711,
3.1415,
]
# Store user data in associated XRECORD:
user_record.commit()
Example 2: Get user data back from the DICTIONARY object
my_dict = doc.rootdict.get_required_dict("MyDict")
entity = my_dict["MyData"]
if isinstance(entity, XRecord):
user_record = UserRecord(entity)
pprint(user_record.data)
Example 3: Store arbitrary binary data
my_dict = doc.rootdict.get_required_dict("MyDict")
xrecord = my_dict.add_xrecord("MyBinaryData")
with BinaryRecord(xrecord) as binary_record:
# The content is stored as hex strings (e.g. ABBAFEFE...) in one or more
# group code 310 tags.
# A preceding group code 160 tag stores the data size in bytes.
data = b"Store any binary data, even line breaks\r\n" * 20
# compress data if required
binary_record.data = zlib.compress(data, level=9)
Example 3: Get binary data back from the DICTIONARY object
entity = my_dict["MyBinaryData"]
if isinstance(entity, XRecord):
binary_record = BinaryRecord(entity)
print("\ncompressed data:")
pprint(binary_record.data)
print("\nuncompressed data:")
pprint(zlib.decompress(binary_record.data))
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!
See also
urecord
moduleUserRecord
classBinaryRecord
class
AppData¶
Application-Defined Data (AppData) was introduced in DXF R13/14 and is used by AutoCAD internally to store the handle to the Extension Dictionary and the 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 Extended Data (XDATA) and the Extension Dictionary are well documented and safe ways to attach custom data to entities.
# register your appid
APPID = "YOUR_UNIQUE_ID"
doc.appids.add(APPID)
# create a DXF entity
line = msp.add_line((0, 0), (1, 0))
# setting the data
line.set_app_data(APPID, [(300, "custom text"), (370, 4711), (460, 3.141592)])
# getting the data
if line.has_app_data(APPID):
tags = line.get_app_data(APPID)
print(f"{str(line)} has {len(tags)} tags of AppData for AppID {APPID!r}")
for tag in tags:
print(tag)
Printed output:
LINE(#30) has 3 tags of AppData for AppID 'YOUR_UNIQUE_ID'
(300, 'custom text')
(370, 4711)
(460, 3.141592)