Skip to content

Commit c73a31b

Browse files
authored
propagate connection geometry from python to c api (#1569)
* add connection geometry to internal json structure * set connection geometry * propagate connector_info to internal json * set ConnectorGeometry for component connectors * set ConnectorGeometry from top level system connectors
1 parent fa661c0 commit c73a31b

3 files changed

Lines changed: 93 additions & 5 deletions

File tree

src/OMSimulatorPython/capi.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,19 @@ class Status(Enum):
1212
fatal = 4
1313
pending = 5
1414

15-
15+
## C structure for connection geometry to properly pass the data from Python to C API
16+
class ssd_connection_geometry_t(ctypes.Structure):
17+
_fields_ = [
18+
("pointsX", ctypes.POINTER(ctypes.c_double)),
19+
("pointsY", ctypes.POINTER(ctypes.c_double)),
20+
("n", ctypes.c_uint)
21+
]
22+
## C structure for connector geometry to properly pass the data from Python to C API
23+
class ssd_connector_geometry_t(ctypes.Structure):
24+
_fields_ = [
25+
("x", ctypes.c_double),
26+
("y", ctypes.c_double)
27+
]
1628
class capi:
1729
def __init__(self):
1830
dirname = os.path.dirname(__file__)
@@ -76,6 +88,10 @@ def __init__(self):
7688
self.obj.oms_setCommandLineOption.restype = ctypes.c_int
7789
self.obj.oms_setConnectionLinearTransformation.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_double, ctypes.c_double]
7890
self.obj.oms_setConnectionLinearTransformation.restype = ctypes.c_int
91+
self.obj.oms_setConnectionGeometry.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(ssd_connection_geometry_t)]
92+
self.obj.oms_setConnectionGeometry.restype = ctypes.c_int
93+
self.obj.oms_setConnectorGeometry.argtypes = [ctypes.c_char_p, ctypes.POINTER(ssd_connector_geometry_t)]
94+
self.obj.oms_setConnectorGeometry.restype = ctypes.c_int
7995
self.obj.oms_setTempDirectory.argtypes = [ctypes.c_char_p]
8096
self.obj.oms_setTempDirectory.restype = ctypes.c_int
8197
self.obj.oms_setExportName.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
@@ -207,6 +223,27 @@ def setConnectionLinearTransformation(self, crefA, crefB, factor, offset):
207223
status = self.obj.oms_setConnectionLinearTransformation(crefA.encode(), crefB.encode(), factor, offset)
208224
return Status(status)
209225

226+
def setConnectionGeometry(self, crefA, crefB, pointsX, pointsY):
227+
'''Set the connection geometry for a connection between two connectors.
228+
The connection geometry is defined by a list of points (pointsX, pointsY) that define the path of the connection in the diagram.'''
229+
n = len(pointsX)
230+
if n != len(pointsY):
231+
raise ValueError("pointsX and pointsY must have the same length")
232+
geometry = ssd_connection_geometry_t(
233+
(ctypes.c_double * n)(*pointsX),
234+
(ctypes.c_double * n)(*pointsY),
235+
n
236+
)
237+
status = self.obj.oms_setConnectionGeometry(crefA.encode(), crefB.encode(), ctypes.byref(geometry))
238+
return Status(status)
239+
240+
def setConnectorGeometry(self, cref, x, y):
241+
'''Set the connector geometry for a connector.
242+
The connector geometry is defined by a point (x, y) that defines the position of the connector in the diagram.'''
243+
geometry = ssd_connector_geometry_t(x, y)
244+
status = self.obj.oms_setConnectorGeometry(cref.encode(), ctypes.byref(geometry))
245+
return Status(status)
246+
210247
def setTempDirectory(self, newTempDir):
211248
status = self.obj.oms_setTempDirectory(newTempDir.encode())
212249
return Status(status)

src/OMSimulatorPython/instantiated_model.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from re import S
21
from OMSimulator.capi import Capi, Status
32
from OMSimulator.component import Component
43
from OMSimulator.componenttable import ComponentTable
@@ -121,6 +120,20 @@ def __init__(self, json_description, system: System, resources: dict):
121120
raise RuntimeError(f"Failed to add oms_addSubModel: {status}")
122121
export_name = ".".join(comp["name"])
123122
#print(f"Setting export name for {comp_path} to {export_name}")
123+
124+
## 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
125+
if "connectors" in comp:
126+
for connector in comp["connectors"]:
127+
if "geometry" in connector:
128+
connector_path = ".".join([comp_path, connector["name"]])
129+
geometry = connector["geometry"]
130+
x = geometry.get("x", 0.0)
131+
y = geometry.get("y", 0.0)
132+
status = Capi.setConnectorGeometry(connector_path, x, y)
133+
if status != Status.ok:
134+
raise RuntimeError(f"Failed to set connector geometry for {connector_path}: {status}")
135+
self.apiCall.append(f'oms_setConnectorGeometry("{connector_path}", {x}, {y})')
136+
124137
if not export_name in self.mappedCrefs:
125138
self.mappedCrefs[export_name] = comp_path
126139
self.mappedCrefs[".".join(comp["name"][:-1])] = solver_path # map parent system too for connector lookup
@@ -154,6 +167,14 @@ def __init__(self, json_description, system: System, resources: dict):
154167
status = Capi.setConnectionLinearTransformation(start, end, float(factor), float(offset))
155168
if status != Status.ok:
156169
raise RuntimeError(f"Failed to set connection linear transformation: {status}")
170+
## add connection geometry if exist
171+
if "connection geometry" in connection:
172+
pointsX = connection["connection geometry"]["pointsX"]
173+
pointsY = connection["connection geometry"]["pointsY"]
174+
self.apiCall.append(f'oms_setConnectionGeometry("{start}", "{end}", {pointsX}, {pointsY})')
175+
status = Capi.setConnectionGeometry(start, end, pointsX, pointsY)
176+
if status != Status.ok:
177+
raise RuntimeError(f"Failed to set connection geometry: {status}")
157178

158179
## set start values
159180
self.setStartValues(self.system.value, self.system.name, self.system.parameterMapping)
@@ -281,6 +302,15 @@ def _addConnector(self, connectors, systemName):
281302
status = Capi.addConnector(connector_path, connector.causality.value, connector.signal_type.value)
282303
if status != Status.ok:
283304
raise RuntimeError(f"Failed to add oms_addConnector:{status}")
305+
## set connector geometry if exist
306+
if connector.connectorGeometry:
307+
x = connector.connectorGeometry.x
308+
y = connector.connectorGeometry.y
309+
self.apiCall.append(f'oms_setConnectorGeometry("{connector_path}", {x}, {y})')
310+
status = Capi.setConnectorGeometry(connector_path, x, y)
311+
if status != Status.ok:
312+
raise RuntimeError(f"Failed to set connector geometry for {connector_path}: {status}")
313+
284314
export_name = systemName
285315
if not export_name in self.mappedCrefs:
286316
self.mappedCrefs[export_name] = connector_path
@@ -293,7 +323,7 @@ def addConnectorFromElements(self, elements, currentSystem):
293323
for key, element in elements.items():
294324
connector_path = ".".join([self.system.name, str(element.name)])
295325
if currentSystem == connector_path:
296-
self._addConnector(element.connectors, connector_path)
326+
self._addConnector(element.connectors, connector_path)
297327

298328
## recurse into subsystem
299329
if isinstance(element, System):

src/OMSimulatorPython/system.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,10 +644,25 @@ def processElements(self, elements_dict: dict, connections: list, data: dict, so
644644
fmuType = fmu.fmuType
645645
else:
646646
fmuType = element.implementation
647+
## add connectors info for the component in the json, this is needed for propagating connector geomtery to capi
648+
connector_info = []
649+
for connector in element.connectors:
650+
connector_info.append({
651+
"name": str(connector.name),
652+
"causality": connector.causality.name if connector.causality else None,
653+
"type": connector.signal_type.name if connector.signal_type else None,
654+
})
655+
## add connector geometry if available
656+
if connector.connectorGeometry:
657+
connector_info[-1]["geometry"] = {
658+
"x": connector.connectorGeometry.x if connector.connectorGeometry else None,
659+
"y": connector.connectorGeometry.y if connector.connectorGeometry else None
660+
}
647661
solver_groups[element.solver].append({
648662
"name": [self.name] + ([systemName] if systemName else []) + [str(element.name)],
649663
"type": fmuType,
650-
"path": str(Path(tempdir, str(element.fmuPath))) if tempdir is not None else str(element.fmuPath)
664+
"path": str(Path(tempdir, str(element.fmuPath))) if tempdir is not None else str(element.fmuPath),
665+
"connectors": connector_info
651666
})
652667
componentSolver[str(element.name)] = element.solver
653668
elif isinstance(element, ComponentTable):
@@ -686,7 +701,13 @@ def processElements(self, elements_dict: dict, connections: list, data: dict, so
686701
"factor": connection.linearTransformation.factor,
687702
"offset": connection.linearTransformation.offset
688703
}
689-
## TODO handle connection geometry
704+
## add connection geometry if available
705+
if connection.connectionGeometry:
706+
connection_info["connection geometry"] = {
707+
"pointsX": connection.connectionGeometry.pointsX,
708+
"pointsY": connection.connectionGeometry.pointsY
709+
}
710+
690711
solver_connections[solver].append(connection_info)
691712

692713
def export(self, root):

0 commit comments

Comments
 (0)