GMF Notation Model for Python¶
GMF is the Graphical Modeling Framework, that allows modeling purely graphical information associated with a diagram of a model. See GMF-Notation.
This tutorial shows how to generate Python/PyEcore code for the GMF metamodel using pyecoregen.
Generating the Metamodel Code¶
First, due to some dependencies expressed in the GMF Notation .ecore
(relative
paths towards other .ecore
), it is required to have the full GMF Notation
hierarchy as well as the EMF source code. Actually, only the folder hierarchy
and the .ecore
contained in the different projects are required.
Using pyecoregen
, the code generation is really easy, and it is performed like
this (under Linux, but the process should be similar for Windows/MacOS):
$ pyecoregen -e notation.ecore -o .
Here, we assume that the notation.ecore
is in the current directory, and the
generated code will be created in the same directory.
Modifying the GMF Notation Metamodel Code¶
Currently, there is a issue in PyEcore/pyecoregen that prevents generating
a metaclass for one of the generated EClass
instances. This case is very specific
and cannot be detected at the moment, so a manual modification is required.
The modification implies the addition of metaclass=MetaEClass
at line 353:
# Before
class View(EModelElement):
# After
class View(EModelElement, metaclass=MetaEClass):
You can now quickly try the generated code by popping a Python REPL:
>>> import notation
>>> notation.Diagram()
<notation.notation.Diagram at 0x7fd50aee55f8>
>>> diag = notation.Diagram(name='my_diag')
>>> diag.name
'my_diag'
Adding Dedicated Behavior¶
The thing with the GMF Notation original project is that some names and behaviors are defined dynamically in the generated Java code. Consequently, in order to match the serialization/deserialization behavior, it is required to add the same behavior to the generated Python code.
First, there are some relations that are dynamically renamed in the code. Thus
persistedChildren
becomes children
and persistedEdges
becomes edges
.
This can be expressed in PyEcore with the following code:
import notation
notation.View.persistedChildren.name = 'children'
notation.View.children = notation.View.persistedChildren
notation.Diagram.persistedEdges.name = 'edges'
notation.Diagram.edges = notation.Diagram.persistedEdges
Then, if we must add a dedicated serialization/deserialization for the
RelativeBendpointList
datatype. In the original code, this datatype is a list
that contains many elements of kind BendPoint
. This type is not expressed in the
metamodel, consequently, we need to add it manually.
class BendPoint(object):
def __init__(self, source_x, source_y, target_x, target_y):
self.source_x = source_x
self.source_y = source_y
self.target_x = target_x
self.target_y = target_y
def __repr__(self):
return '[{}, {}, {}, {}]'.format(self.source_x, self.source_y,
self.target_x, self.target_y)
Then, we must add a dedicated function that will be used for deserializing
a list of BendPoint
. If we look through a GMF Notation Model example, here
is how a list of BendPoint
is serialized:
<bendpoints ... points="[4, 0, 56, 53]$[4, -24, 56, 29]$[-62, -24, -10, 29]$[-62, -53, -10, 0]"/>
Each BendPoint
is separated by a $
, and the numbers in [...]
represents the source x, source y, target x, target y coordinates. One possible
function that will take this string and create a list of BendPoint
is then:
And a function to serializing an existing list of BendPoint
is then:
def to_str(bp_list):
return '$'.join([repr(bp) for bp in bp_list])
Then, these functions are registered as custom serializer/deserializer for the
RelativeBendpointList
datatype:
Loading a GMF Notation Model¶
Loading an existing GMF Notation Model is then quite easy using the PyEcore API:
from pyecore.resources import ResourceSet
import notation
# insert code with the custom serializer/deserializer...etc
rset = ResourceSet()
rset.metamodel_registry[notation.nsURI] = notation # register the notation metamodel
resource = rset.get_resource('my_notation_file.xmi')
root = resource.contents[0]
Now, root
contains the root of your GMF Notation model.
## Modifying a GMF Notation Model
You can now modify the model, add elements, etc. using the PyEcore API:
root.name = 'my_new_name' # Changing model name
# ...
Saving the Modified Model¶
When you need to save your modified model, if your original model serializes
its type using the xsi:type
field and you want to keep the same behavior,
you need to add an option as shown here.
from pyecore.resources.xmi import XMIOptions
options = {
XMIOptions.OPTION_USE_XMI_TYPE: True
}
resource.save(options=options)
If you want to save your model in a different file, you can save the resource this way instead:
options = {
XMIOptions.OPTION_USE_XMI_TYPE: True
}
resource.save('my_other_file.xmi', options=options)
This way, your original model file will not be overridden.