Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion src/OMSimulatorPython/capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@ class Status(Enum):
fatal = 4
pending = 5


## C structure for connection geometry to properly pass the data from Python to C API
class ssd_connection_geometry_t(ctypes.Structure):
_fields_ = [
("pointsX", ctypes.POINTER(ctypes.c_double)),
("pointsY", ctypes.POINTER(ctypes.c_double)),
("n", ctypes.c_uint)
]
## C structure for connector geometry to properly pass the data from Python to C API
class ssd_connector_geometry_t(ctypes.Structure):
_fields_ = [
("x", ctypes.c_double),
("y", ctypes.c_double)
]
class capi:
def __init__(self):
dirname = os.path.dirname(__file__)
Expand Down Expand Up @@ -76,6 +88,10 @@ def __init__(self):
self.obj.oms_setCommandLineOption.restype = ctypes.c_int
self.obj.oms_setConnectionLinearTransformation.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_double, ctypes.c_double]
self.obj.oms_setConnectionLinearTransformation.restype = ctypes.c_int
self.obj.oms_setConnectionGeometry.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(ssd_connection_geometry_t)]
self.obj.oms_setConnectionGeometry.restype = ctypes.c_int
self.obj.oms_setConnectorGeometry.argtypes = [ctypes.c_char_p, ctypes.POINTER(ssd_connector_geometry_t)]
self.obj.oms_setConnectorGeometry.restype = ctypes.c_int
self.obj.oms_setTempDirectory.argtypes = [ctypes.c_char_p]
self.obj.oms_setTempDirectory.restype = ctypes.c_int
self.obj.oms_setExportName.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
Expand Down Expand Up @@ -207,6 +223,27 @@ def setConnectionLinearTransformation(self, crefA, crefB, factor, offset):
status = self.obj.oms_setConnectionLinearTransformation(crefA.encode(), crefB.encode(), factor, offset)
return Status(status)

def setConnectionGeometry(self, crefA, crefB, pointsX, pointsY):
'''Set the connection geometry for a connection between two connectors.
The connection geometry is defined by a list of points (pointsX, pointsY) that define the path of the connection in the diagram.'''
n = len(pointsX)
if n != len(pointsY):
raise ValueError("pointsX and pointsY must have the same length")
geometry = ssd_connection_geometry_t(
(ctypes.c_double * n)(*pointsX),
(ctypes.c_double * n)(*pointsY),
n
)
status = self.obj.oms_setConnectionGeometry(crefA.encode(), crefB.encode(), ctypes.byref(geometry))
return Status(status)

def setConnectorGeometry(self, cref, x, y):
'''Set the connector geometry for a connector.
The connector geometry is defined by a point (x, y) that defines the position of the connector in the diagram.'''
geometry = ssd_connector_geometry_t(x, y)
status = self.obj.oms_setConnectorGeometry(cref.encode(), ctypes.byref(geometry))
return Status(status)

def setTempDirectory(self, newTempDir):
status = self.obj.oms_setTempDirectory(newTempDir.encode())
return Status(status)
Expand Down
34 changes: 32 additions & 2 deletions src/OMSimulatorPython/instantiated_model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from re import S
from OMSimulator.capi import Capi, Status
from OMSimulator.component import Component
from OMSimulator.componenttable import ComponentTable
Expand Down Expand Up @@ -121,6 +120,20 @@ def __init__(self, json_description, system: System, resources: dict):
raise RuntimeError(f"Failed to add oms_addSubModel: {status}")
export_name = ".".join(comp["name"])
#print(f"Setting export name for {comp_path} to {export_name}")

## parse connector geometry for the component if exist and set it to capi after adding the component, this is needed for proper mapping of connector geometry
if "connectors" in comp:
for connector in comp["connectors"]:
if "geometry" in connector:
connector_path = ".".join([comp_path, connector["name"]])
geometry = connector["geometry"]
x = geometry.get("x", 0.0)
y = geometry.get("y", 0.0)
status = Capi.setConnectorGeometry(connector_path, x, y)
if status != Status.ok:
raise RuntimeError(f"Failed to set connector geometry for {connector_path}: {status}")
self.apiCall.append(f'oms_setConnectorGeometry("{connector_path}", {x}, {y})')

if not export_name in self.mappedCrefs:
self.mappedCrefs[export_name] = comp_path
self.mappedCrefs[".".join(comp["name"][:-1])] = solver_path # map parent system too for connector lookup
Expand Down Expand Up @@ -154,6 +167,14 @@ def __init__(self, json_description, system: System, resources: dict):
status = Capi.setConnectionLinearTransformation(start, end, float(factor), float(offset))
if status != Status.ok:
raise RuntimeError(f"Failed to set connection linear transformation: {status}")
## add connection geometry if exist
if "connection geometry" in connection:
pointsX = connection["connection geometry"]["pointsX"]
pointsY = connection["connection geometry"]["pointsY"]
self.apiCall.append(f'oms_setConnectionGeometry("{start}", "{end}", {pointsX}, {pointsY})')
status = Capi.setConnectionGeometry(start, end, pointsX, pointsY)
if status != Status.ok:
raise RuntimeError(f"Failed to set connection geometry: {status}")

## set start values
self.setStartValues(self.system.value, self.system.name, self.system.parameterMapping)
Expand Down Expand Up @@ -281,6 +302,15 @@ def _addConnector(self, connectors, systemName):
status = Capi.addConnector(connector_path, connector.causality.value, connector.signal_type.value)
if status != Status.ok:
raise RuntimeError(f"Failed to add oms_addConnector:{status}")
## set connector geometry if exist
if connector.connectorGeometry:
x = connector.connectorGeometry.x
y = connector.connectorGeometry.y
self.apiCall.append(f'oms_setConnectorGeometry("{connector_path}", {x}, {y})')
status = Capi.setConnectorGeometry(connector_path, x, y)
if status != Status.ok:
raise RuntimeError(f"Failed to set connector geometry for {connector_path}: {status}")

export_name = systemName
if not export_name in self.mappedCrefs:
self.mappedCrefs[export_name] = connector_path
Expand All @@ -293,7 +323,7 @@ def addConnectorFromElements(self, elements, currentSystem):
for key, element in elements.items():
connector_path = ".".join([self.system.name, str(element.name)])
if currentSystem == connector_path:
self._addConnector(element.connectors, connector_path)
self._addConnector(element.connectors, connector_path)

## recurse into subsystem
if isinstance(element, System):
Expand Down
25 changes: 23 additions & 2 deletions src/OMSimulatorPython/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,10 +644,25 @@ def processElements(self, elements_dict: dict, connections: list, data: dict, so
fmuType = fmu.fmuType
else:
fmuType = element.implementation
## add connectors info for the component in the json, this is needed for propagating connector geomtery to capi
connector_info = []
for connector in element.connectors:
connector_info.append({
"name": str(connector.name),
"causality": connector.causality.name if connector.causality else None,
"type": connector.signal_type.name if connector.signal_type else None,
})
## add connector geometry if available
if connector.connectorGeometry:
connector_info[-1]["geometry"] = {
"x": connector.connectorGeometry.x if connector.connectorGeometry else None,
"y": connector.connectorGeometry.y if connector.connectorGeometry else None
}
solver_groups[element.solver].append({
"name": [self.name] + ([systemName] if systemName else []) + [str(element.name)],
"type": fmuType,
"path": str(Path(tempdir, str(element.fmuPath))) if tempdir is not None else str(element.fmuPath)
"path": str(Path(tempdir, str(element.fmuPath))) if tempdir is not None else str(element.fmuPath),
"connectors": connector_info
})
componentSolver[str(element.name)] = element.solver
elif isinstance(element, ComponentTable):
Expand Down Expand Up @@ -686,7 +701,13 @@ def processElements(self, elements_dict: dict, connections: list, data: dict, so
"factor": connection.linearTransformation.factor,
"offset": connection.linearTransformation.offset
}
## TODO handle connection geometry
## add connection geometry if available
if connection.connectionGeometry:
connection_info["connection geometry"] = {
"pointsX": connection.connectionGeometry.pointsX,
"pointsY": connection.connectionGeometry.pointsY
}

solver_connections[solver].append(connection_info)

def export(self, root):
Expand Down