Scripted objects migration: Difference between revisions

From FreeCAD Documentation
(→‎Old object and new object: This new module will refer to the same type of object, but both the module name as well as the class name have been renamed. Moreover, the properties also have changed; one property has been renamed, and a completely new property has been added.)
(→‎Migration by redirecting the class: More information on the migration)
Line 109: Line 109:
== Migration by redirecting the class ==
== Migration by redirecting the class ==


In the older module the original class needs to be redirected to point to the new class.
In the older module the original class is deleted, and a redirection is used to point to the new class.


</translate>
</translate>
Line 120: Line 120:
<translate>
<translate>


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 {{incode|OldObject}} will be immediately redirected to the new object.
Any older document that tries to load {{incode|OldObject}} will be immediately redirected to load {{incode|new_module.NewObject}}.

If we inspect the properties of the object in the [[Python_console|Python console]] we will see that the older properties are conserved, but the object has a new Proxy class.
</translate>
{{Code|code=
>>> obj = App.ActiveDocument.Custom
>>> print(obj.PropertiesList)
['Area', ..., ..., ..., 'Length', ..., ..., ..., ...]
>>> print(obj.Proxy)
<objects.new_module.NewObject object at 0x7f099700b2b0>
}}
<translate>

However, in this case we don't see the new properties of the new class. The reason is that the older object didn't have these properties. When the {{incode|OldObject}} was redirected to {{incode|new_module.NewObject}}, only the proxy class changed, but all previous data was retained.


== Migration when restoring the document ==
== Migration when restoring the document ==

Revision as of 00:58, 9 May 2020

This page is a work in progress. Please do not edit or translate until this message is removed.

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.

<Document SchemaVersion="4" ProgramVersion="0.19R20959 (Git)" FileVersion="1">
    ...
    <Properties Count="15" TransientCount="3">
    ...
    </Properties>
    <Objects Count="1" Dependencies="1">
        <ObjectDeps Name="Custom" Count="0"/>
        <Object type="Part::FeaturePython" name="Custom" id="2715" Touched="1" />
    </Objects>
    <ObjectData Count="1">
        <Object name="Custom">
            <Properties Count="9" TransientCount="0">
                ...
                <Property name="Proxy" type="App::PropertyPythonObject" status="1">
                    <Python value="eyJUeXBlIjogIkN1c3RvbSJ9" encoded="yes" module="old_module" class="OldObject"/>
                </Property>
                ...
            </Properties>
        </Object>
    </ObjectData>
</Document>

Particularly focus on this part:

...
                <Property name="Proxy" type="App::PropertyPythonObject" status="1">
                    <Python value="eyJUeXBlIjogIkN1c3RvbSJ9" encoded="yes" module="old_module" class="OldObject"/>
                </Property>
                ...

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 original code, for example, when restructuring an entire workbench. 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):
        obj.addProperty("App::PropertyLength", "Length")
        obj.addProperty("App::PropertyArea", "Area")
        obj.Length = 15
        obj.Area = 300
        obj.Proxy = self
        self.Type = "Custom"

    def execute(self, obj):
        pass

An old object was previously created and saved in my_document.FCstd.

import FreeCAD as App
import old_module

doc = App.newDocument()
doc.FileName = "my_document.FCStd"

obj = doc.addObject("Part::FeaturePython", "Custom")
old_module.OldObject(obj)
if App.GuiUp:
    obj.ViewObject.Proxy=1
doc.save()

Now we consider that the workbench is restructured, so that classes aren't just at the root directory, but instead are inside an objects directory. Complex workbenches that have many different types of objects should be structured in directories including objects, viewproviders, Gui Commands, task panel interfaces, etc.

# objects/new_module.py
class NewObject:
    def __init__(self, obj):
        obj.addProperty("App::PropertyLength", "Length")
        obj.addProperty("App::PropertyArea", "GeneralArea")
        obj.addProperty("App::PropertyInteger", "Divisions")
        obj.Length = 30
        obj.GeneralArea = 600
        obj.Divisions = 4
        obj.Proxy = self
        self.Type = "Custom"

    def execute(self, obj):
        pass

This new module will refer to the same type of object, but both the module name as well as the class name have been renamed. Moreover, the properties also have changed; one property has been renamed, and a completely new property has been added.

Migration by redirecting the class

In the older module the original class is deleted, and a redirection is used to point to the new class.

# old_module.py
import objects.new_module as new_module

OldObject = new_module.NewObject

Any older document that tries to load OldObject will be immediately redirected to load new_module.NewObject.

If we inspect the properties of the object in the Python console we will see that the older properties are conserved, but the object has a new Proxy class.

>>> obj = App.ActiveDocument.Custom
>>> print(obj.PropertiesList)
['Area', ..., ..., ..., 'Length', ..., ..., ..., ...]
>>> print(obj.Proxy)
<objects.new_module.NewObject object at 0x7f099700b2b0>

However, in this case we don't see the new properties of the new class. The reason is that the older object didn't have these properties. When the OldObject was redirected to new_module.NewObject, only the proxy class changed, but all previous data was retained.

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