Scripted objects migration
Introduction
Scripted objects are rebuilt every time a FCStd document is opened. To do this the document keeps a reference to the module and Python class that were used to create the object, along with the properties and values of that object.
If the module or class is no longer present, the object will fail to load correctly. This means that once an object is created using a particular class, the module should no longer be moved or renamed because if this is done, previously saved objects will break.
However, a valid reason for moving or renaming the class is to improve the structure and maintainability of the code. In this case there are various strategies to migrate old objects to using a new class. This is done in order to retain backwards compatibility, when outright breaking of old documents must be avoided.
Old object and new object
An old object is defined in a module which is at the root of the workbench.
# old_module.py
class OldObject:
def __init__(self):
obj.addProperty("App::PropertyLength", "Length")
obj.addProperty("App::PropertyArea", "Area")
def execute(self):
pass
An old object was previously created and saved in my_document.FCstd.
import old_module
doc = App.newDocument()
doc.FileName = "my_document.FCStd"
obj = doc.addObject("Part::FeaturePython", "Custom")
old_module.OldObject(obj)
doc.save()
The workbench is restructured, so that all classes are inside an objects package.
# objects/new_module.py
class NewObject:
def __init__(self):
obj.addProperty("App::PropertyLength", "Length")
obj.addProperty("App::PropertyArea", "Area")
obj.addProperty("App::PropertyInteger", "Divisions")
obj.Proxy = self
self.Type = "Custom"
def execute(self):
pass
import objects.new_module as new_module
doc = App.newDocument()
doc.FileName = "my_new_document.FCStd"
obj = doc.addObject("Part::FeaturePython", "Custom")
new_module.NewObject(obj)
doc.save()
Migration by redirecting the class
In the older module the original class needs to be redirected to point to the new class.
# old_module.py
import objects.new_module as new_module
OldObject = new_module.NewObject
This old module no longer contains the definition of the class; now it just points to the new class, so that any older document that tries to load OldObject
will be immediately redirected to the new object.
Migration when restoring the document
In the older module the original class just contains a method to migrate the object.
The onDocumentRestored
method, if implemented, will run when the document tries to restore an object that uses the OldObject
class.
# old_module.py
import FreeCAD as App
import objects.new_module as new_module
import viewp.new_view as new_view
class OldObject:
def onDocumentRestored(self, obj):
if hasattr(obj, "Proxy") and obj.Proxy.Type == "Custom":
_module = str(obj.Proxy.__class__)
_module = _module.lstrip("<class '").rstrip("'>")
if _module == "old_module.OldObject":
self._migrate(obj, _module)
def _migrate(self, obj, _module):
new_module.NewObject(obj)
if App.GuiUp:
new_view.ViewProviderNew(obj.ViewObject)
Links
- FreeCAD scripting: Python, Introduction to Python, Python scripting tutorial, FreeCAD Scripting Basics
- Modules: Builtin modules, Units, Quantity
- Workbenches: Workbench creation, Gui Commands, Commands, Installing more workbenches
- Meshes and Parts: Mesh Scripting, Topological data scripting, Mesh to Part, PythonOCC
- Parametric objects: Scripted objects, Viewproviders (Custom icon in tree view)
- Scenegraph: Coin (Inventor) scenegraph, Pivy
- Graphical interface: Interface creation, Interface creation completely in Python (1, 2, 3, 4, 5), PySide, PySide examples beginner, intermediate, advanced
- Macros: Macros, How to install macros
- Embedding: Embedding FreeCAD, Embedding FreeCADGui
- Other: Expressions, Code snippets, Line drawing function, FreeCAD vector math library (deprecated)
- Hubs: User hub, Power users hub, Developer hub