How to Serialize Custom Data in FreeCAD
This article provides a practical guide on serializing and
deserializing custom data structures within FreeCAD. You will learn how
to leverage FreeCAD’s native property framework, implement state
restoration using Python proxy objects via __getstate__ and
__setstate__, and use C++ Save and
Restore routines for low-level property
implementations.
Method 1: Using Built-in FreeCAD Properties
The most robust way to manage custom data in FreeCAD is to use native
App::Property types. Native properties automatically handle
serialization into the FreeCAD XML/document archive
(.FCStd) when saved and restored.
If your data can be expressed as primitive structures (lists,
dictionaries, or strings), store it as a JSON-encoded string within an
App::PropertyString:
import json
import FreeCAD as App
# Create a generic feature object
obj = App.ActiveDocument.addObject("App::FeaturePython", "CustomDataHolder")
# Add a string property to hold serialized data
obj.addProperty("App::PropertyString", "CustomPayload", "Data", "Stores serialized JSON")
# Serialization
data_to_store = {"node_ids": [101, 102, 103], "tolerance": 0.005, "enabled": True}
obj.CustomPayload = json.dumps(data_to_store)
# Deserialization
retrieved_data = json.loads(obj.CustomPayload)
print(retrieved_data["tolerance"])Method 2:
Python Proxy State (__getstate__ and
__setstate__)
When building scripted objects (FeaturePython),
non-property attributes attached to your Python proxy class are not
saved by default. FreeCAD uses Python's standard pickle
mechanism via __getstate__ and __setstate__ to
persist internal proxy attributes.
import FreeCAD as App
class CustomFeature:
def __init__(self, obj):
obj.Proxy = self
self.internal_matrix = [[1, 0], [0, 1]]
self.version = 1.0
def execute(self, obj):
pass
def __getstate__(self):
"""Serialize custom proxy variables into a dictionary/tuple."""
return {
"internal_matrix": self.internal_matrix,
"version": self.version
}
def __setstate__(self, state):
"""Deserialize and restore state when the document is opened."""
self.internal_matrix = state.get("internal_matrix", [])
self.version = state.get("version", 1.0)
# Instantiation
doc = App.ActiveDocument
feature = doc.addObject("App::FeaturePython", "StatefulObject")
CustomFeature(feature)
doc.recompute()When saving the document, FreeCAD calls __getstate__ and
writes the resulting byte stream to the .FCStd container.
When reopening, it instantiates the class and calls
__setstate__.
Method 3: Handling Large Binary Structures (External Document Files)
If you have large binary payloads (such as raw point clouds or mesh
arrays) that should not inflate the main Document.xml file,
you can hook into FreeCAD's document stream writers.
In Python scripted objects, implement SaveDocFile and
RestoreDocFile:
class BinaryDataFeature:
def __init__(self, obj):
obj.Proxy = self
self.raw_bytes = b"\x00\x01\x02\x03\x04"
def SaveDocFile(self, file_path):
"""Called by FreeCAD during document save. Writes to an auxiliary file in the archive."""
with open(file_path, "wb") as f:
f.write(self.raw_bytes)
def RestoreDocFile(self, file_path):
"""Called by FreeCAD during document load."""
with open(file_path, "rb") as f:
self.raw_bytes = f.read()Method 4: C++ Custom Property Implementation
When developing compiled C++ extensions, you inherit from
App::Property and override the XML read/write methods:
#include <App/Property.h>
#include <Base/Writer.h>
#include <Base/XMLReader.h>
class PropertyCustomData : public App::Property
{
TYPESYSTEM_HEADER();
public:
std::vector<int> values;
void Save(Base::Writer &writer) const override {
writer.Stream() << writer.ind() << "<CustomData count=\"" << values.size() << "\">";
for (int v : values) {
writer.Stream() << v << " ";
}
writer.Stream() << "</CustomData>\n";
}
void Restore(Base::XMLReader &reader) override {
reader.readElement("CustomData");
int count = reader.getAttributeAsInteger("count");
values.clear();
values.reserve(count);
for (int i = 0; i < count; ++i) {
int val;
reader >> val;
values.push_back(val);
}
reader.readEndElement("CustomData");
}
};This ensures your C++ data types integrate directly with FreeCAD's XML parser and schema validator.