Commit 015790c5 authored by Jan Dinkelbach's avatar Jan Dinkelbach
Browse files

initial commit of cimpy package

parents
# python folders
*.egg-info/
__pycache__
.idea
./cimpy/__pycache__
# log files
*.log
# CIMgen
Python tool for generation and modification of CIM data
# Installation
```
$ git clone https://git.rwth-aachen.de/acs/core/cim/cimgen.git
$ cd cimgen
$ python setup.py develop
```
# Usage
## CIM Import
Function for creating CIMpy objects out of a CIM topology.
```
cimpy.cim_import(xml_files, cim_version, start_dict=None)
Arguments:
xml_files: List of CIM RDF/XML files
cim_version: String containing the CIM version
Optional Arguments:
start_dict: List of CIM classes which should be read, default: read all classes
Output:
res: A map containing the CIMpy objects with the UUID as map key
```
[Example for CIM Import](https://git.rwth-aachen.de/acs/core/cim/cimgen/blob/master/examples/quickstart/readCIGREMV.py)
## CIM Export
Function for serialization of CIMpy objects to XML files.
```
cimpy.cim_export(res, namespaces_dict, file_name, version):
Arguments:
res: A dictionary containing all CIMpy objects accessible via the UUID
namespaces_dict: A dictionary containing the namespaces of the original XML files
file_name: String containing the name for the XML files.
version: String containing the CGMES version
Output:
One XML file for each package in the CGMES version. The package name is added to the file name like [file_name]_[package].xml
```
[Example for CIM Export](https://git.rwth-aachen.de/acs/core/cim/cimgen/blob/master/examples/quickstart/exportCIGREMV.py)
\ No newline at end of file
from cimpy.cimexport import cim_export
from cimpy.cimimport import cim_import
import cimpy.utils
\ No newline at end of file
import os
import importlib
import chevron
from datetime import datetime
import copy
# Input Object dictionary
def _create_package_classes_dict(res):
package_classes_dict = {}
for key in res.keys():
# objects of type: cimpy.cimversion.package_name.file_name.class_name
package = str(type(res[key])).split('.')[2]
class_name = str(type(res[key])).split('.')[-1]
class_name = class_name.split("'")[0]
if package not in package_classes_dict.keys():
package_classes_dict[package] = []
package_classes_dict[package].append({class_name: res[key], 'mRID': key})
else:
package_classes_dict[package].append({class_name: res[key], 'mRID': key})
return package_classes_dict
# Input class dictionary
def _get_class_attributes(object_list, version):
i = 0
print_list = []
about_dict = {}
for elem in object_list:
for key in elem.keys():
if key == "mRID":
continue
hit_dict = {}
no_hit_dict = {}
class_attributes = _get_attributes(elem[key])
class_attributes_with_references, mRID = _get_reference_uuid(class_attributes, version)
if mRID == '':
mRID = elem['mRID']
hit_dict['mRID'] = mRID
hit_dict['name'] = key
no_hit_dict['mRID'] = mRID
no_hit_dict['name'] = key
no_hit_list = class_attributes_with_references
# cyclic references like TransformerEnd <-> PowerTransformer both in output file
search_class = elem[key]
while search_class.__class__.__name__ != 'Base':
no_hit_copy = no_hit_list
if hasattr(search_class, 'reference_dict'):
no_hit_list = []
for attr_elem in no_hit_copy:
hit = False
for package, value in search_class.reference_dict.items():
package_name = package.split('Version')[0]
if attr_elem['attr_name'].split('.')[1] in value:
hit = True
# hit_list.append(attr_elem)
hit_dict['attributes'] = attr_elem
if package_name in about_dict.keys():
hit_copy = copy.deepcopy(hit_dict)
about_dict[package_name].append(hit_copy)
else:
hit_copy = copy.deepcopy(hit_dict)
about_dict[package_name] = [hit_copy]
break
if not hit:
no_hit_list.append(attr_elem)
if len(no_hit_list) == 0:
break
search_class = search_class.__class__.__bases__[0]()
# process about attributes
# if len(hit_list) > 0:
# hit_dict['attributes'] = hit_list
# if package_name in about_dict.keys():
# about_dict[package_name].append(hit_dict)
# else:
# about_dict[package_name] = [hit_dict]
if len(no_hit_list) > 0:
# process other attributes
no_hit_dict['attributes'] = no_hit_list
print_list.append(no_hit_dict)
return print_list, about_dict
# Input Attributes Dictionary
def _get_reference_uuid(attr_dict, version):
reference_list = []
base_class_name = 'cimpy.' + version + '.Base'
base_module = importlib.import_module(base_class_name)
base_class = getattr(base_module, 'Base')
mRID = ''
UUID = ''
for key in attr_dict:
attributes = {}
if isinstance(attr_dict[key], list):
array = []
for elem in attr_dict[key]:
if issubclass(type(elem), base_class):
UUID = '%' + elem.mRID
array.append(UUID)
# resource = key + ' rdf:resource='
# reference_dict[resource] = array
else:
print('Error!')
if len(array) == 1:
attributes['value'] = array[0]
else:
attributes['value'] = array
elif issubclass(type(attr_dict[key]), base_class):
# resource = key + ' rdf:resource='
UUID = '%' + attr_dict[key].mRID
attributes['value'] = UUID
elif key == 'IdentifiedObject.mRID':
mRID = attr_dict[key]
elif attr_dict[key] == "" or attr_dict[key] is None:
pass
else:
# reference_dict[key] = attr_dict[key]
attributes['value'] = attr_dict[key]
attributes['attr_name'] = key
if 'value' in attributes.keys():
if isinstance(attributes['value'], list):
for reference_item in attributes['value']:
if reference_item not in ['', None, 0.0]:
reference_list.append({'value': reference_item, 'attr_name': key})
elif attributes['value'] not in ['', None, 0.0]:
reference_list.append(attributes)
return reference_list, mRID
def _set_attribute_or_reference(text, render):
result = render(text)
result = result.split('@')
value = result[0]
attr_name = result[1]
if '%' in value:
reference = value.split('%')[1]
return ' rdf:resource="#' + reference + '"/>'
else:
return '>' + value + '</cim:' + attr_name + '>'
def _set_attribute_or_reference_model(text, render):
result = render(text)
result = result.split('@')
value = result[0]
attr_name = result[1]
if '%' in value:
reference = value.split('%')[1]
return ' rdf:resource="' + reference + '"/>'
else:
return '>' + value + '</md:Model.' + attr_name + '>'
def _create_namespaces_list(namespaces_dict):
namespaces_list = []
for key in namespaces_dict:
namespace = {}
namespace['key'] = key
namespace['url'] = namespaces_dict[key]
namespaces_list.append(namespace)
return namespaces_list
def cim_export(res, namespaces_dict, file_name, version):
"""Function for serialization of cgmes classes
This function serializes cgmes classes with the template engine chevron. The classes are separated by their package
and one xml file for each package is created. The package name is added after the file name. The
set_attributes_or_reference function is a lamda function for chevron to decide whether the value of an attribute is
a reference to another class object or not.
:param res: a dictionary containing the cgmes classes accessible via the mRID
:param namespaces_dict: a dictionary containing the RDF namespaces used in the imported xml files
:param file_name: a string with the name of the xml files which will be created
:param version: cgmes version, e.g. version = "cimgen_v2_4_15"
"""
package_classes_dict = _create_package_classes_dict(res)
export_dict = {}
cwd = os.getcwd()
os.chdir(os.path.dirname(__file__))
about_list = []
for key in package_classes_dict.keys():
export_dict[key], about_dict = _get_class_attributes(package_classes_dict[key], version)
about_list.append(about_dict)
new_export_dict = {}
for key in export_dict.keys():
new_export_dict[key] = {}
new_export_dict[key]['classes'] = export_dict[key]
for about_dict in about_list:
for about_key in about_dict.keys():
if about_key in new_export_dict.keys():
if 'about' in new_export_dict[about_key].keys():
new_export_dict[about_key]['about'].append(about_dict[about_key])
else:
new_export_dict[about_key]['about'] = about_dict[about_key]
else:
new_export_dict[about_key] = {'about': about_dict[about_key]}
namespaces_list = _create_namespaces_list(namespaces_dict)
created = {'attr_name': 'created', 'value': datetime.now().strftime("%d/%m/%Y %H:%M:%S")}
authority = {'attr_name': 'modelingAuthoritySet', 'value': 'www.acs.eonerc.rwth-aachen.de'}
for key in new_export_dict.keys():
model_name = {'mRID': file_name, 'description': []}
model_description = {'model': [model_name]}
model_description['model'][0]['description'].append(created)
model_description['model'][0]['description'].append(authority)
if 'classes' in new_export_dict[key].keys():
classes = new_export_dict[key]['classes']
else:
classes = False
if 'about' in new_export_dict[key].keys():
about = new_export_dict[key]['about']
else:
about = False
full_file_name = file_name + '_' + key + '.xml'
full_path = os.path.join(cwd, full_file_name)
profile = {'attr_name': 'profile', 'value': key}
model_description['model'][0]['description'].append(profile)
if not os.path.exists(full_path):
with open(full_path, 'w') as file:
with open('export_template.mustache') as f:
output = chevron.render(f, {"classes": classes,
"about": about,
"set_attributes_or_reference": _set_attribute_or_reference,
"set_attributes_or_reference_model": _set_attribute_or_reference_model,
"namespaces": namespaces_list,
"model": model_description['model']})
file.write(output)
del model_description, model_name
os.chdir(cwd)
def _get_attributes(class_object):
# list containing all parent classes after while loop
inheritance_list = [class_object]
class_type = type(class_object)
parent = class_object
# get parent class, ignore base class (class without attributes)
while 'Base.Base' not in str(class_type):
parent = parent.__class__.__bases__[0]()
# insert parent class at beginning of list, classes inherit from top to bottom
inheritance_list.insert(0, parent)
class_type = type(parent)
# dictionary containing all attributes with key: 'Class_Name.Attribute_Name'
attributes_dict = {}
# __dict__ of a subclass returns also the attributes of the parent classes
# to avoid multiple attributes create list with all attributes already processed
attributes_list = []
for parent_class in inheritance_list:
# get all attributes of the current parent class
parent_attributes_dict = parent_class.__dict__
class_name = parent_class.__class__.__name__
for key in parent_attributes_dict.keys():
if key not in attributes_list:
attributes_list.append(key)
attributes_name = class_name + '.' + key
attributes_dict[attributes_name] = getattr(class_object, key)
else:
continue
return attributes_dict
class Base():
"""
Base Class for CIM
"""
def __init__(self, *args, **kw_args):
pass
def printxml(self, dict={}):
return dict
from cimpy.cimgen_v2_4_15.Equipment.IdentifiedObject import IdentifiedObject
class Diagram(IdentifiedObject):
'''
The diagram being exchanged. The coordinate system is a standard Cartesian coordinate system and the orientation attribute defines the orientation.
:DiagramStyle: A Diagram may have a DiagramStyle. Default: None
:orientation: Coordinate system orientation of the diagram. Default: None
:x1InitialView: X coordinate of the first corner of the initial view. Default: 0.0
:x2InitialView: X coordinate of the second corner of the initial view. Default: 0.0
:y1InitialView: Y coordinate of the first corner of the initial view. Default: 0.0
:y2InitialView: Y coordinate of the second corner of the initial view. Default: 0.0
:DiagramElements: A diagram is made up of multiple diagram objects. Default: []
'''
__doc__ += '\n Documentation of parent class IdentifiedObject: \n' + IdentifiedObject.__doc__
def __init__(self, DiagramStyle = None, orientation = None, x1InitialView = 0.0, x2InitialView = 0.0, y1InitialView = 0.0, y2InitialView = 0.0, DiagramElements = [], *args, **kw_args):
super().__init__(*args, **kw_args)
self.DiagramStyle = DiagramStyle
self.orientation = orientation
self.x1InitialView = x1InitialView
self.x2InitialView = x2InitialView
self.y1InitialView = y1InitialView
self.y2InitialView = y2InitialView
self.DiagramElements = DiagramElements
def __str__(self):
str = 'class=Diagram\n'
attributes = self.__dict__
for key in attributes.keys():
str = str + key + '={}\n'.format(attributes[key])
return str
from cimpy.cimgen_v2_4_15.Base import Base
class DiagramLayoutVersion(Base):
'''
Version details.
:baseUML: Base UML provided by CIM model manager. Default: ''
:baseURI: Profile URI used in the Model Exchange header and defined in IEC standards. It uniquely identifies the Profile and its version. It is given for information only and to identify the closest IEC profile to which this CGMES profile is based on. Default: ''
:date: Profile creation date Form is YYYY-MM-DD for example for January 5, 2009 it is 2009-01-05. Default: ''
:differenceModelURI: Difference model URI defined by IEC 61970-552. Default: ''
:entsoeUML: UML provided by ENTSO-E. Default: ''
:entsoeURI: Profile URI defined by ENTSO-E and used in the Model Exchange header. It uniquely identifies the Profile and its version. The last two elements in the URI (http://entsoe.eu/CIM/DiagramLayout/yy/zzz) indicate major and minor versions where: - yy - indicates a major version; - zzz - indicates a minor version. Default: ''
:modelDescriptionURI: Model Description URI defined by IEC 61970-552. Default: ''
:namespaceRDF: RDF namespace. Default: ''
:namespaceUML: CIM UML namespace. Default: ''
:shortName: The short name of the profile used in profile documentation. Default: ''
'''
def __init__(self, baseUML = '', baseURI = '', date = '', differenceModelURI = '', entsoeUML = '', entsoeURI = '', modelDescriptionURI = '', namespaceRDF = '', namespaceUML = '', shortName = '', ):
self.baseUML = baseUML
self.baseURI = baseURI
self.date = date
self.differenceModelURI = differenceModelURI
self.entsoeUML = entsoeUML
self.entsoeURI = entsoeURI
self.modelDescriptionURI = modelDescriptionURI
self.namespaceRDF = namespaceRDF
self.namespaceUML = namespaceUML
self.shortName = shortName
def __str__(self):
str = 'class=DiagramLayoutVersion\n'
attributes = self.__dict__
for key in attributes.keys():
str = str + key + '={}\n'.format(attributes[key])
return str
from cimpy.cimgen_v2_4_15.Equipment.IdentifiedObject import IdentifiedObject
class DiagramObject(IdentifiedObject):
'''
An object that defines one or more points in a given space. This object can be associated with anything that specializes IdentifiedObject. For single line diagrams such objects typically include such items as analog values, breakers, disconnectors, power transformers, and transmission lines.
:Diagram: A diagram object is part of a diagram. Default: None
:drawingOrder: The drawing order of this element. The higher the number, the later the element is drawn in sequence. This is used to ensure that elements that overlap are rendered in the correct order. Default: 0.0
:isPolygon: Defines whether or not the diagram objects points define the boundaries of a polygon or the routing of a polyline. If this value is true then a receiving application should consider the first and last points to be connected. Default: False
:offsetX: The offset in the X direction. This is used for defining the offset from centre for rendering an icon (the default is that a single point specifies the centre of the icon). The offset is in per-unit with 0 indicating there is no offset from the horizontal centre of the icon. -0.5 indicates it is offset by 50% to the left and 0.5 indicates an offset of 50% to the right. Default: 0.0
:offsetY: The offset in the Y direction. This is used for defining the offset from centre for rendering an icon (the default is that a single point specifies the centre of the icon). The offset is in per-unit with 0 indicating there is no offset from the vertical centre of the icon. The offset direction is dependent on the orientation of the diagram, with -0.5 and 0.5 indicating an offset of +/- 50% on the vertical axis. Default: 0.0
:rotation: Sets the angle of rotation of the diagram object. Zero degrees is pointing to the top of the diagram. Rotation is clockwise. Default: 0.0
:IdentifiedObject: The diagram objects that are associated with the domain object. Default: None
:DiagramObjectPoints: A diagram object can have 0 or more points to reflect its layout position, routing (for polylines) or boundary (for polygons). Default: []
:VisibilityLayers: A diagram object can be part of multiple visibility layers. Default: []
:DiagramObjectStyle: A diagram object has a style associated that provides a reference for the style used in the originating system. Default: None
'''
__doc__ += '\n Documentation of parent class IdentifiedObject: \n' + IdentifiedObject.__doc__
def __init__(self, Diagram = None, drawingOrder = 0.0, isPolygon = False, offsetX = 0.0, offsetY = 0.0, rotation = 0.0, IdentifiedObject = None, DiagramObjectPoints = [], VisibilityLayers = [], DiagramObjectStyle = None, *args, **kw_args):
super().__init__(*args, **kw_args)
self.Diagram = Diagram
self.drawingOrder = drawingOrder
self.isPolygon = isPolygon
self.offsetX = offsetX
self.offsetY = offsetY
self.rotation = rotation
self.IdentifiedObject = IdentifiedObject
self.DiagramObjectPoints = DiagramObjectPoints
self.VisibilityLayers = VisibilityLayers
self.DiagramObjectStyle = DiagramObjectStyle
def __str__(self):
str = 'class=DiagramObject\n'
attributes = self.__dict__
for key in attributes.keys():
str = str + key + '={}\n'.format(attributes[key])
return str
from cimpy.cimgen_v2_4_15.Base import Base
class DiagramObjectGluePoint(Base):
'''
This is used for grouping diagram object points from different diagram objects that are considered to be glued together in a diagram even if they are not at the exact same coordinates.
:DiagramObjectPoints: The 'glue' point to which this point is associated. Default: []
'''
def __init__(self, DiagramObjectPoints = [], ):
self.DiagramObjectPoints = DiagramObjectPoints
def __str__(self):
str = 'class=DiagramObjectGluePoint\n'
attributes = self.__dict__
for key in attributes.keys():
str = str + key + '={}\n'.format(attributes[key])
return str
from cimpy.cimgen_v2_4_15.Base import Base
class DiagramObjectPoint(Base):
'''
A point in a given space defined by 3 coordinates and associated to a diagram object. The coordinates may be positive or negative as the origin does not have to be in the corner of a diagram.
:DiagramObject: The diagram object with which the points are associated. Default: None
:DiagramObjectGluePoint: A diagram object glue point is associated with 2 or more object points that are considered to be 'glued' together. Default: None
:sequenceNumber: The sequence position of the point, used for defining the order of points for diagram objects acting as a polyline or polygon with more than one point. Default: 0.0
:xPosition: The X coordinate of this point. Default: 0.0
:yPosition: The Y coordinate of this point. Default: 0.0
:zPosition: The Z coordinate of this point. Default: 0.0
'''
def __init__(self, DiagramObject = None, DiagramObjectGluePoint = None, sequenceNumber = 0.0, xPosition = 0.0, yPosition = 0.0, zPosition = 0.0, ):
self.DiagramObject = DiagramObject
self.DiagramObjectGluePoint = DiagramObjectGluePoint
self.sequenceNumber = sequenceNumber
self.xPosition = xPosition
self.yPosition = yPosition
self.zPosition = zPosition
def __str__(self):
str = 'class=DiagramObjectPoint\n'
attributes = self.__dict__
for key in attributes.keys():
str = str + key + '={}\n'.format(attributes[key])
return str
from cimpy.cimgen_v2_4_15.Equipment.IdentifiedObject import IdentifiedObject
class DiagramObjectStyle(IdentifiedObject):
'''
A reference to a style used by the originating system for a diagram object. A diagram object style describes information such as line thickness, shape such as circle or rectangle etc, and color.
:StyledObjects: A style can be assigned to multiple diagram objects. Default: []
'''
__doc__ += '\n Documentation of parent class IdentifiedObject: \n' + IdentifiedObject.__doc__
def __init__(self, StyledObjects = [], *args, **kw_args):
super().__init__(*args, **kw_args)
self.StyledObjects = StyledObjects
def __str__(self):
str = 'class=DiagramObjectStyle\n'
attributes = self.__dict__
for key in attributes.keys():
str = str + key + '={}\n'.format(attributes[key])
return str
from cimpy.cimgen_v2_4_15.Equipment.IdentifiedObject import IdentifiedObject
class DiagramStyle(IdentifiedObject):
'''
The diagram style refer to a style used by the originating system for a diagram. A diagram style describes information such as schematic, geographic, bus-branch etc.
:Diagram: A DiagramStyle can be used by many Diagrams. Default: []
'''
__doc__ += '\n Documentation of parent class IdentifiedObject: \n' + IdentifiedObject.__doc__
def __init__(self, Diagram = [], *args, **kw_args):
super().__init__(*args, **kw_args)
self.Diagram = Diagram
def __str__(self):
str = 'class=DiagramStyle\n'
attributes = self.__dict__
for key in attributes.keys():
str = str + key + '={}\n'.format(attributes[key])
return str
from cimpy.cimgen_v2_4_15.Base import Base
</