basicemergesolverhelperpackage 0.0.3__tar.gz → 0.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (14) hide show
  1. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/PKG-INFO +1 -1
  2. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/setup.py +1 -1
  3. basicemergesolverhelperpackage-0.0.5/src/basicemergesolverhelperpackage/EMergeHelperFunctions.py +513 -0
  4. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/src/basicemergesolverhelperpackage.egg-info/PKG-INFO +1 -1
  5. basicemergesolverhelperpackage-0.0.3/src/basicemergesolverhelperpackage/EMergeHelperFunctions.py +0 -248
  6. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/LICENSE +0 -0
  7. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/README.md +0 -0
  8. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/setup.cfg +0 -0
  9. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/src/basicemergesolverhelperpackage/EMergeConstants.py +0 -0
  10. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/src/basicemergesolverhelperpackage/__init__.py +0 -0
  11. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/src/basicemergesolverhelperpackage.egg-info/SOURCES.txt +0 -0
  12. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/src/basicemergesolverhelperpackage.egg-info/dependency_links.txt +0 -0
  13. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/src/basicemergesolverhelperpackage.egg-info/requires.txt +0 -0
  14. {basicemergesolverhelperpackage-0.0.3 → basicemergesolverhelperpackage-0.0.5}/src/basicemergesolverhelperpackage.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basicemergesolverhelperpackage
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Mesh creation and plot utilities for EMerge solver or other FEM solver.
5
5
  Home-page: https://github.com/LubomirJagos42/basic-emerge-solver-helper-package
6
6
  Author: Lubomir Jagos
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="basicemergesolverhelperpackage",
8
- version="0.0.3",
8
+ version="0.0.5",
9
9
  author="Lubomir Jagos",
10
10
  author_email="lubomir.jagos.42@gmail.com",
11
11
  description="Mesh creation and plot utilities for EMerge solver or other FEM solver.",
@@ -0,0 +1,513 @@
1
+ import emerge as em
2
+ import emerge._emerge.geometry as emergeGeo
3
+ import emerge._emerge.physics.microwave.microwave_bc as emergeMicrowaveBC
4
+ from emerge._emerge.physics.microwave.microwave_data import MWData #this is for simulationResult object literal specification for python
5
+
6
+ from typing import Callable, Literal
7
+ import gmsh
8
+ import os
9
+
10
+ import numpy as np
11
+ from emerge.plot import smith, plot_sp
12
+
13
+ class EMergeHelperFunctions:
14
+ simulationObj = None
15
+ materialList = {}
16
+ portList = {}
17
+ _generatedPortIndex = 1
18
+ _temporaryInternalPortIndex = 1 #this shouldn't exists, but it's helper counter if port somehow will be from more objects
19
+
20
+ def __init__(self, simulationObj):
21
+ self.simulationObj = simulationObj
22
+ print("EMerge helper created")
23
+
24
+ def getAllObjectByName(self, name: str):
25
+ resultObjList = []
26
+ for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
27
+ if geometryObj.name == name or geometryObj.name.startswith(name+"_"):
28
+ resultObjList.append(geometryObj)
29
+
30
+ return resultObjList
31
+
32
+ def getObjectSurface(self, name: str):
33
+ boundaryObjList = []
34
+ for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
35
+ if geometryObj.name == name or geometryObj.name.startswith(name+"_"):
36
+ if isinstance(geometryObj, emergeGeo.GeoSurface):
37
+ boundaryObjList.append(geometryObj)
38
+ else:
39
+ boundaryObjList.append(geometryObj.boundary())
40
+
41
+ return boundaryObjList
42
+
43
+ def getObjectVolume(self, name: str):
44
+ resultObjList = []
45
+ for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
46
+ if geometryObj.name == name or geometryObj.name.startswith(name+"_"):
47
+ if isinstance(geometryObj, emergeGeo.GeoVolume):
48
+ resultObjList.append(geometryObj)
49
+
50
+ return resultObjList
51
+
52
+ def importStepFile(self, name:str, filename:str,directory:list[str] | str = "", unit:float=1.0, priority:int=-1, materialName:str = ""):
53
+ targetDirectory:str = ""
54
+ if directory != "" and directory != []:
55
+ if type(directory) == str:
56
+ targetDirectory = directory
57
+ elif type(directory) == list:
58
+ for dirName in directory:
59
+ targetDirectory = os.path.join(targetDirectory, dirName)
60
+
61
+ stepObjectGroup = em.geo.step.STEPItems(name=name, filename=os.path.join(targetDirectory, filename), unit=unit)
62
+
63
+ for geoObj in stepObjectGroup.objects:
64
+ geoObj.prio_set(priority)
65
+ if materialName != "":
66
+ geoObj.set_material(self.materialList[materialName])
67
+
68
+ def setObjSize(self, name:str, size:float):
69
+ objectList = self.getAllObjectByName(name)
70
+ for obj in objectList:
71
+ self.simulationObj.mesher.set_size(obj, size)
72
+
73
+ def setObjBoundarySize(self, name:str, size:float):
74
+ objectList = self.getObjectSurface(name)
75
+ for obj in objectList:
76
+ self.simulationObj.mesher.set_boundary_size(obj, size)
77
+
78
+ def setObjFaceSize(self, name:str, size:float):
79
+ objectList = self.getObjectSurface(name)
80
+ for obj in objectList:
81
+ self.simulationObj.mesher.set_face_size(obj, size)
82
+
83
+ def setObjVolumeSize(self, name:str, size:float):
84
+ objectList = self.getObjectVolume(name)
85
+ for obj in objectList:
86
+ self.simulationObj.mesher.set_domain_size(obj, size)
87
+
88
+ def setLumpedElementToObject(
89
+ self,
90
+ name: str,
91
+ impedance_function: Callable | None = None,
92
+ width: float | None = None,
93
+ height: float | None = None,
94
+ ):
95
+ objectList = self.getObjectSurface(name)
96
+ for obj in objectList:
97
+ self.simulationObj.mw.bc.LumpedElement(face=obj, impedance_function=impedance_function, width=width, height=height)
98
+
99
+ def setBoundaryConditionToObject(self, name: str, type: str):
100
+ objectList = self.getObjectSurface(name)
101
+ for obj in objectList:
102
+ if type.lower() == "absorbing":
103
+ self.simulationObj.mw.bc.AbsorbingBoundary(obj)
104
+ elif type == "PEC":
105
+ self.simulationObj.mw.bc.PEC(obj)
106
+ elif type == "PMC":
107
+ self.simulationObj.mw.bc.PMC(obj)
108
+ else:
109
+ raise Exception(f"ERROR: Unknown type of boundary condition: {type}")
110
+
111
+ def createGmshNamedGroup(self, geometryObjName: str, groupName: str, groupTag: int = -1, useBoundary: bool = False, useSuffixToRecognizeGeometryName: bool = True):
112
+ objectTag1DList = []
113
+ objectTag2DList = []
114
+ objectTag3DList = []
115
+
116
+ for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
117
+ if geometryObj.name == geometryObjName or geometryObj.name.startswith(geometryObjName + ('_' if useSuffixToRecognizeGeometryName else '')):
118
+ for tagTuple in (geometryObj.boundary().dimtags if (useBoundary and not isinstance(geometryObj, emergeGeo.GeoSurface)) else geometryObj.dimtags):
119
+ if tagTuple[0] == 1:
120
+ objectTag1DList.append(tagTuple[1])
121
+ if tagTuple[0] == 2:
122
+ objectTag2DList.append(tagTuple[1])
123
+ if tagTuple[0] == 3:
124
+ objectTag3DList.append(tagTuple[1])
125
+
126
+ if groupTag > -1:
127
+ gmsh.model.addPhysicalGroup(1, objectTag1DList, name=groupName, tag=groupTag)
128
+ gmsh.model.addPhysicalGroup(2, objectTag2DList, name=groupName, tag=groupTag + 1)
129
+ gmsh.model.addPhysicalGroup(3, objectTag3DList, name=groupName, tag=groupTag + 2)
130
+ else:
131
+ gmsh.model.addPhysicalGroup(1, objectTag1DList, name=groupName)
132
+ gmsh.model.addPhysicalGroup(2, objectTag2DList, name=groupName)
133
+ gmsh.model.addPhysicalGroup(3, objectTag3DList, name=groupName)
134
+
135
+ def addMaterial(self, name, materialObj, color="#000000", opacity: float = 1.0):
136
+ self.materialList[name] = materialObj
137
+ self.materialList[name].color = color
138
+ self.materialList[name].opacity = opacity
139
+
140
+ def getMaterial(self, name):
141
+ #
142
+ # Get material from internal material list
143
+ #
144
+ materialObj = self.materialList[name] if name in self.materialList.keys() else None
145
+
146
+ #
147
+ # If material not found try to scan all geometries and their assigned materials if it will be found
148
+ #
149
+ if materialObj == None:
150
+ for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
151
+ if geometryObj.material.name == name:
152
+ materialObj = geometryObj.material
153
+ break
154
+
155
+ return materialObj
156
+
157
+ def setMaterialColor(self, name, color="#000000", opacity: float = 1.0):
158
+ """Setter for color and opacity
159
+ :param name: Name of material
160
+ :param color: Color string in html for like #FF0000 (red)
161
+ :param opacity: Makes material transparent (0.0) or non-transparent (1.0)
162
+ """
163
+ self.materialList[name].color = color
164
+ self.materialList[name].opacity = opacity
165
+
166
+ def addPort(
167
+ self,
168
+ name="",
169
+ portStart=[0.0, 0.0, 0.0],
170
+ width=0.0,
171
+ height=0.0,
172
+ R=50.0,
173
+ direction=em.ZAX,
174
+ excitationAmplitude:float=0.0,
175
+ geometryObject:em._emerge.geometry.GeoObject=None,
176
+ portNumber:int=-1,
177
+
178
+ modalModeType: Literal['TE','TM','TEM'] | None = None,
179
+ modalMixedMaterials: bool = False,
180
+ modalImpedanceDefinition: Literal['PV','PI','VI'] = 'PV',
181
+
182
+ rectangularWaveguideMode: tuple[int, int] = (0, 0),
183
+ rectangularWaveguidePermittivity: float = 1.0,
184
+
185
+ coaxPortInnerRadius: float = 0.0,
186
+ coaxPortOuterRadius: float = 0.0,
187
+ coaxPortPermittivity: float = 1.0,
188
+ ):
189
+ self.portList[name] = {}
190
+ self.portList[name]['portStart'] = portStart
191
+ self.portList[name]['width'] = width
192
+ self.portList[name]['height'] = height
193
+ self.portList[name]['R'] = R
194
+ self.portList[name]['direction'] = direction
195
+ self.portList[name]['excitationAmplitude'] = excitationAmplitude
196
+ self.portList[name]['object'] = geometryObject
197
+ self.portList[name]['portNumber'] = self._generatedPortIndex if portNumber == -1 else portNumber
198
+
199
+ self.portList[name]["modalModeType"] = modalModeType
200
+ self.portList[name]["modalMixedMaterials"] = modalMixedMaterials
201
+ self.portList[name]["modalImpedanceDefinition"] = modalImpedanceDefinition
202
+
203
+ self.portList[name]["rectangularWaveguideMode"] = rectangularWaveguideMode
204
+ self.portList[name]["rectangularWaveguidePermittivity"] = rectangularWaveguidePermittivity
205
+
206
+ self.portList[name]["coaxPortInnerRadius"] = coaxPortInnerRadius
207
+ self.portList[name]["coaxPortOuterRadius"] = coaxPortOuterRadius
208
+ self.portList[name]["coaxPortPermittivity"] = coaxPortPermittivity
209
+
210
+ if portNumber == -1:
211
+ self._generatedPortIndex += 1
212
+
213
+ def addLumpedPort(
214
+ self,
215
+ name = "",
216
+ portStart = [0.0, 0.0, 0.0],
217
+ width = 0.0,
218
+ height = 0.0,
219
+ R = 50.0,
220
+ direction = em.ZAX,
221
+ power:float = 0.0,
222
+ geometryObject:em._emerge.geometry.GeoObject = None,
223
+ portNumber:int = -1
224
+ ):
225
+ self.addPort(
226
+ name=name,
227
+ portStart=portStart,
228
+ width=width,
229
+ height=height,
230
+ R=R,
231
+ direction=direction,
232
+ excitationAmplitude=power,
233
+ geometryObject=geometryObject,
234
+ portNumber=portNumber
235
+ )
236
+
237
+ def addModalPort(
238
+ self,
239
+ name = "",
240
+ mode: Literal["TE", "TM", "TEM"] = "TE",
241
+ mixedMaterials: bool = False,
242
+ impedanceDefinition: Literal["PV", "PI", "VI"] = "PV",
243
+ power:float = 0.0,
244
+ geometryObject:em._emerge.geometry.GeoObject = None,
245
+ portNumber:int = -1
246
+ ):
247
+ self.addPort(
248
+ name=name,
249
+ modalModeType=mode,
250
+ modalMixedMaterials=mixedMaterials,
251
+ modalImpedanceDefinition=impedanceDefinition,
252
+ excitationAmplitude=power,
253
+ geometryObject=geometryObject,
254
+ portNumber=portNumber
255
+ )
256
+
257
+ def addRectangularWaveguidePort(
258
+ self,
259
+ name = "",
260
+ mode: tuple[int, int] = (0,0),
261
+ er: float = 1.0,
262
+ power:float = 1.0,
263
+ geometryObject:em._emerge.geometry.GeoObject = None,
264
+ portNumber:int = -1
265
+ ):
266
+ self.addPort(
267
+ name=name,
268
+ rectangularWaveguideMode = mode,
269
+ rectangularWaveguidePermittivity = er,
270
+ excitationAmplitude=power,
271
+ geometryObject=geometryObject,
272
+ portNumber=portNumber
273
+ )
274
+
275
+ def addCoaxPort(
276
+ self,
277
+ name = "",
278
+ inner_radius: float = 0.0,
279
+ outer_radius: float = 0.0,
280
+ er: float = 1.0,
281
+ power:float = 1.0,
282
+ geometryObject:em._emerge.geometry.GeoObject = None,
283
+ portNumber:int = -1
284
+ ):
285
+ self.addPort(
286
+ name=name,
287
+ coaxPortInnerRadius = inner_radius,
288
+ coaxPortOuterRadius = outer_radius,
289
+ coaxPortPermittivity = er,
290
+ excitationAmplitude=power,
291
+ geometryObject=geometryObject,
292
+ portNumber=portNumber
293
+ )
294
+
295
+
296
+ def getPort(self, name):
297
+ return self.portList[name]
298
+
299
+ def getPortByNumber(self, portNumber):
300
+ resultPortObj = None
301
+ for portObj in self.portList:
302
+ if portNumber == portObj['portNumber']:
303
+ resultPortObj = portObj
304
+ return resultPortObj
305
+
306
+ def getPortNumber(self, name):
307
+ for portObj in self.portList:
308
+ if portObj['portNumber'] == name:
309
+ return portObj['portNumber']
310
+
311
+ def setPortAsLumpedPort(self, name, searchObjectName="") -> list[emergeMicrowaveBC.LumpedPort]:
312
+ portObj = self.getPort(name)
313
+ resultBoundaryConditionList = []
314
+
315
+ #
316
+ # Port object can be splitted since there was fragmentation operation in EMerge
317
+ #
318
+ portGeometryObjectList = self.getAllObjectByName(name if searchObjectName == "" else searchObjectName)
319
+ for geometryObj in portGeometryObjectList:
320
+ if portObj['excitationAmplitude'] > 0.0:
321
+ resultObj = self.simulationObj.mw.bc.LumpedPort(
322
+ face=geometryObj,
323
+ port_number=portObj['portNumber'],
324
+ width=portObj['width'],
325
+ height=portObj['height'],
326
+ direction=portObj['direction'],
327
+ Z0=portObj['R'],
328
+ power=portObj['excitationAmplitude']
329
+ )
330
+ else:
331
+ resultObj = self.simulationObj.mw.bc.LumpedPort(
332
+ face=geometryObj,
333
+ port_number=portObj['portNumber'],
334
+ width=portObj['width'],
335
+ height=portObj['height'],
336
+ direction=portObj['direction'],
337
+ Z0=portObj['R']
338
+ )
339
+ resultBoundaryConditionList.append(resultObj)
340
+
341
+ self._temporaryInternalPortIndex += 1
342
+
343
+ return resultBoundaryConditionList
344
+
345
+ def setPortAsModalPort(self, name, searchObjectName="") -> list[emergeMicrowaveBC.ModalPort]:
346
+ """Experimental implementation not tested on real world example!!!"""
347
+
348
+ resultBoundaryConditionList = []
349
+
350
+ portObj = self.getPort(name)
351
+ portGeometryObjectList = self.getAllObjectByName(name if searchObjectName == "" else searchObjectName)
352
+
353
+ for geometryObj in portGeometryObjectList:
354
+ resultObj = self.simulationObj.mw.bc.ModalPort(
355
+ face = geometryObj,
356
+ port_number=portObj['portNumber'],
357
+ power = portObj['excitationAmplitude'],
358
+ modetype = portObj["modalModeType"],
359
+ number_of_modes = 1,
360
+ mixed_materials = portObj["modalMixedMaterials"],
361
+ impedance_definition = portObj["modalImpedanceDefinition"]
362
+ )
363
+ resultBoundaryConditionList.append(resultObj)
364
+
365
+ return resultBoundaryConditionList
366
+
367
+ def setPortAsRectangularWaveguidePort(self, name, searchObjectName="") -> list[emergeMicrowaveBC.RectangularWaveguide]:
368
+ """Experimental implementation not tested on real world example!!!"""
369
+
370
+ resultBoundaryConditionList = []
371
+
372
+ portObj = self.getPort(name)
373
+ portGeometryObjectList = self.getAllObjectByName(name if searchObjectName == "" else searchObjectName)
374
+
375
+ for geometryObj in portGeometryObjectList:
376
+ resultObj = self.simulationObj.mw.bc.RectangularWaveguide(
377
+ face = geometryObj,
378
+ port_number=portObj['portNumber'],
379
+ power = portObj['excitationAmplitude'],
380
+ mode = portObj["rectangularWaveguideMode"],
381
+ er = portObj["rectangularWaveguidePermittivity"]
382
+ )
383
+ resultBoundaryConditionList.append(resultObj)
384
+
385
+ return resultBoundaryConditionList
386
+
387
+ def setPortAsCoaxPort(self, name, searchObjectName="") -> list[emergeMicrowaveBC.CoaxPort]:
388
+ """Experimental implementation not tested on real world example!!!"""
389
+
390
+ resultBoundaryConditionList = []
391
+
392
+ portObj = self.getPort(name)
393
+ portGeometryObjectList = self.getAllObjectByName(name if searchObjectName == "" else searchObjectName)
394
+
395
+ for geometryObj in portGeometryObjectList:
396
+ resultObj = self.simulationObj.mw.bc.CoaxPort(
397
+ face = geometryObj,
398
+ port_number=portObj['portNumber'],
399
+ power = portObj['excitationAmplitude'],
400
+ rad_in_out = (portObj["coaxPortInnerRadius"], portObj["coaxPortInnerRadius"]),
401
+ er = portObj["coaxPortPermittivity"]
402
+ )
403
+ resultBoundaryConditionList.append(resultObj)
404
+
405
+ return resultBoundaryConditionList
406
+
407
+ def plotSParamUsingPortName(self, sourcePortName, targetPortName, dblim=[-40, 0], plotSmithChart=False):
408
+ sourcePortNumber = self.getPortNumber(sourcePortName)
409
+ targetPortNumber = self.getPortNumber(targetPortName)
410
+
411
+ self.plotSParamUsingPortNumbers(sourcePortNumber, targetPortNumber, dblim, plotSmithChart)
412
+
413
+ def plotSParamUsingPortNumbers(self, sourcePortNumber, targetPortNumber, dblim=[-40, 0], xunit="GHz", plotSmithChart=False, plotInterpolatedPoints:int=-1, plotS11=False):
414
+ simulationResult = self.simulationObj.data.mw
415
+
416
+ freqs = simulationResult.scalar.grid.freq
417
+ fmin = freqs.min()
418
+ fmax = freqs.max()
419
+
420
+ if plotInterpolatedPoints > 0:
421
+ #
422
+ # Add points into frequency axis and interpolate computed S param over these points it makes graph line smooth but it can provide wrong result!!!
423
+ #
424
+ freq_dense = np.linspace(fmin, fmax, plotInterpolatedPoints)
425
+ S_data = simulationResult.scalar.grid.model_S(sourcePortNumber, targetPortNumber, freq_dense)
426
+ plotLabel = f'S{sourcePortNumber}{targetPortNumber}'
427
+ plot_sp(freq_dense, S_data, labels=plotLabel, dblim=dblim)
428
+ else:
429
+ S21_data = simulationResult.scalar.grid.S(sourcePortNumber, targetPortNumber)
430
+ S11_data = simulationResult.scalar.grid.S(sourcePortNumber, sourcePortNumber)
431
+ plotLabel_S11 = f'S{sourcePortNumber}{sourcePortNumber}'
432
+ plotLabel_S21 = f'S{targetPortNumber}{sourcePortNumber}'
433
+ if plotS11:
434
+ plot_sp(freqs, [S11_data, S21_data], labels=[plotLabel_S11, plotLabel_S21], dblim=dblim, xunit=xunit)
435
+ else:
436
+ plot_sp(freqs, [S21_data], labels=[plotLabel_S21], dblim=dblim, xunit=xunit)
437
+
438
+ if plotSmithChart:
439
+ smith(S_data, f=freq_dense, labels=plotLabel) # smith chart
440
+
441
+ def addObjectToView(self, nameOrList: str | list, opacity:float=0.1):
442
+ objectList = []
443
+ if type(nameOrList) == str:
444
+ objectList = self.getAllObjectByName(nameOrList)
445
+ if type(nameOrList) == list:
446
+ for oneName in nameOrList:
447
+ objectList.extend(self.getAllObjectByName(oneName))
448
+
449
+ for geoObject in objectList:
450
+ self.simulationObj.display.add_object(geoObject, opacity=opacity)
451
+
452
+ def create_emerge_plane_data(self, port_start, port_stop, normal):
453
+ """
454
+ Computes origin, u, and v vectors for an EMerge Plane using
455
+ start/stop diagonal points and a surface normal vector.
456
+
457
+ Inputs can be FreeCAD vectors or standard (x, y, z) tuples.
458
+ """
459
+ # 1. Convert everything to numpy arrays for clean math
460
+ p1 = np.array([port_start[0], port_start[1], port_start[2]])
461
+ p4 = np.array([port_stop[0], port_stop[1], port_stop[2]])
462
+ n = np.array([normal[0], normal[1], normal[2]])
463
+
464
+ # Normalize the normal vector to ensure it is a unit vector
465
+ n = n / np.linalg.norm(n)
466
+
467
+ # 2. Calculate the full diagonal vector across the port
468
+ diag = p4 - p1
469
+
470
+ # 3. Project the diagonal vector to eliminate any component pointing
471
+ # along the normal (ensures the math stays strictly flat on the 2D plane)
472
+ diag_planar = diag - np.dot(diag, n) * n
473
+
474
+ # 4. Determine the primary coordinate alignment for the 'u' axis.
475
+ # We choose an axis that isn't parallel to our normal vector.
476
+ if abs(n[0]) < 0.9:
477
+ ref_dir = np.array([1.0, 0.0, 0.0]) # Fallback to X axis alignment
478
+ else:
479
+ ref_dir = np.array([0.0, 1.0, 0.0]) # Fallback to Y axis alignment
480
+
481
+ # Generate an orthogonal direction for 'u' using a cross product
482
+ u_direction = np.cross(n, ref_dir)
483
+ u_axis = u_direction / np.linalg.norm(u_direction)
484
+
485
+ # Generate the perpendicular 'v' direction
486
+ v_axis = np.cross(n, u_axis)
487
+
488
+ # 5. Project the planar diagonal onto our newly established u and v axes
489
+ u_magnitude = np.dot(diag_planar, u_axis)
490
+ v_magnitude = np.dot(diag_planar, v_axis)
491
+
492
+ # 6. Reconstruct the final u and v vectors as clean 3D tuples
493
+ u = tuple(u_axis * u_magnitude)
494
+ v = tuple(v_axis * v_magnitude)
495
+ origin = tuple(p1)
496
+
497
+ return origin, u, v
498
+
499
+ def exportCSV_SParam(self, filename: str, simulationResult: MWData, sourcePortNumber: int, targetPortNumber: int, useMagnitude=True):
500
+ # get frequency axis from result
501
+ freq = simulationResult.scalar.grid.freq
502
+
503
+ # get S param, if magnitude compute it, normaly it's complex number
504
+ if useMagnitude:
505
+ sParam = 20 * np.log10(abs(simulationResult.scalar.grid.S(sourcePortNumber, targetPortNumber)))
506
+ else:
507
+ sParam = simulationResult.scalar.grid.S(sourcePortNumber, targetPortNumber)
508
+
509
+ outFile = open(filename, "w")
510
+ outFile.write("{},{}\n".format("freq", f"s{targetPortNumber}{sourcePortNumber}"))
511
+ for x in zip(freq, sParam):
512
+ outFile.write("{},{}\n".format(x[0], x[1]))
513
+ outFile.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basicemergesolverhelperpackage
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Mesh creation and plot utilities for EMerge solver or other FEM solver.
5
5
  Home-page: https://github.com/LubomirJagos42/basic-emerge-solver-helper-package
6
6
  Author: Lubomir Jagos
@@ -1,248 +0,0 @@
1
- import emerge as em
2
- import emerge._emerge.geometry as emergeGeo
3
- from typing import Callable
4
- import gmsh
5
- import os
6
-
7
- import numpy as np
8
- from emerge.plot import smith, plot_sp
9
-
10
- class EMergeHelperFunctions:
11
- simulationObj = None
12
- materialList = {}
13
- portList = {}
14
- _generatedPortIndex = 1
15
- _temporaryInternalPortIndex = 1 #this shouldn't exists, but it's helper counter if port somehow will be from more objects
16
-
17
- def __init__(self, simulationObj):
18
- self.simulationObj = simulationObj
19
- print("EMerge helper created")
20
-
21
- def getAllObjectByName(self, name: str):
22
- resultObjList = []
23
- for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
24
- if geometryObj.name == name or geometryObj.name.startswith(name+"_"):
25
- resultObjList.append(geometryObj)
26
-
27
- return resultObjList
28
-
29
- def getObjectSurface(self, name: str):
30
- boundaryObjList = []
31
- for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
32
- if geometryObj.name == name or geometryObj.name.startswith(name+"_"):
33
- if isinstance(geometryObj, emergeGeo.GeoSurface):
34
- boundaryObjList.append(geometryObj)
35
- else:
36
- boundaryObjList.append(geometryObj.boundary())
37
-
38
- return boundaryObjList
39
-
40
- def getObjectVolume(self, name: str):
41
- resultObjList = []
42
- for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
43
- if geometryObj.name == name or geometryObj.name.startswith(name+"_"):
44
- if isinstance(geometryObj, emergeGeo.GeoVolume):
45
- resultObjList.append(geometryObj)
46
-
47
- return resultObjList
48
-
49
- def importStepFile(self, name:str, filename:str,directory:list[str] | str = "", unit:float=1.0, priority:int=-1, materialName:str = ""):
50
- targetDirectory:str = ""
51
- if directory != "" and directory != []:
52
- if type(directory) == str:
53
- targetDirectory = directory
54
- elif type(directory) == list:
55
- for dirName in directory:
56
- targetDirectory = os.path.join(targetDirectory, dirName)
57
-
58
- stepObjectGroup = em.geo.step.STEPItems(name=name, filename=os.path.join(targetDirectory, filename), unit=unit)
59
-
60
- for geoObj in stepObjectGroup.objects:
61
- geoObj.prio_set(priority)
62
- if materialName != "":
63
- geoObj.set_material(self.materialList[materialName])
64
-
65
- def setObjSize(self, name:str, size:float):
66
- objectList = self.getAllObjectByName(name)
67
- for obj in objectList:
68
- self.simulationObj.mesher.set_size(obj, size)
69
-
70
- def setObjBoundarySize(self, name:str, size:float):
71
- objectList = self.getObjectSurface(name)
72
- for obj in objectList:
73
- self.simulationObj.mesher.set_boundary_size(obj, size)
74
-
75
- def setObjFaceSize(self, name:str, size:float):
76
- objectList = self.getObjectSurface(name)
77
- for obj in objectList:
78
- self.simulationObj.mesher.set_face_size(obj, size)
79
-
80
- def setObjVolumeSize(self, name:str, size:float):
81
- objectList = self.getObjectVolume(name)
82
- for obj in objectList:
83
- self.simulationObj.mesher.set_domain_size(obj, size)
84
-
85
- def setLumpedElementToObject(
86
- self,
87
- name: str,
88
- impedance_function: Callable | None = None,
89
- width: float | None = None,
90
- height: float | None = None,
91
- ):
92
- objectList = self.getObjectSurface(name)
93
- for obj in objectList:
94
- self.simulationObj.mw.bc.LumpedElement(face=obj, impedance_function=impedance_function, width=width, height=height)
95
-
96
- def setBoundaryConditionToObject(self, name: str, type: str):
97
- objectList = self.getObjectSurface(name)
98
- for obj in objectList:
99
- if type.lower() == "absorbing":
100
- self.simulationObj.mw.bc.AbsorbingBoundary(obj)
101
- elif type == "PEC":
102
- self.simulationObj.mw.bc.PEC(obj)
103
- elif type == "PMC":
104
- self.simulationObj.mw.bc.PMC(obj)
105
- else:
106
- raise Exception(f"ERROR: Unknown type of boundary condition: {type}")
107
-
108
- def createGmshNamedGroup(self, geometryObjName: str, groupName: str, groupTag: int = -1, useBoundary: bool = False, useSuffixToRecognizeGeometryName: bool = True):
109
- objectTag1DList = []
110
- objectTag2DList = []
111
- objectTag3DList = []
112
-
113
- for geometryObj in self.simulationObj.state.manager.geometry_list[self.simulationObj.modelname].values():
114
- if geometryObj.name == geometryObjName or geometryObj.name.startswith(geometryObjName + ('_' if useSuffixToRecognizeGeometryName else '')):
115
- for tagTuple in (geometryObj.boundary().dimtags if (useBoundary and not isinstance(geometryObj, emergeGeo.GeoSurface)) else geometryObj.dimtags):
116
- if tagTuple[0] == 1:
117
- objectTag1DList.append(tagTuple[1])
118
- if tagTuple[0] == 2:
119
- objectTag2DList.append(tagTuple[1])
120
- if tagTuple[0] == 3:
121
- objectTag3DList.append(tagTuple[1])
122
-
123
- if groupTag > -1:
124
- gmsh.model.addPhysicalGroup(1, objectTag1DList, name=groupName, tag=groupTag)
125
- gmsh.model.addPhysicalGroup(2, objectTag2DList, name=groupName, tag=groupTag + 1)
126
- gmsh.model.addPhysicalGroup(3, objectTag3DList, name=groupName, tag=groupTag + 2)
127
- else:
128
- gmsh.model.addPhysicalGroup(1, objectTag1DList, name=groupName)
129
- gmsh.model.addPhysicalGroup(2, objectTag2DList, name=groupName)
130
- gmsh.model.addPhysicalGroup(3, objectTag3DList, name=groupName)
131
-
132
- def addMaterial(self, name, materialObj, color="#000000", opacity: float = -89.0):
133
- self.materialList[name] = materialObj
134
- self.materialList[name].color = color
135
- self.materialList[name].opacity = opacity
136
-
137
- def setMaterialColor(self, name, color="#000000", opacity: float = -89.0):
138
- """Setter for color and opacity
139
- :param name: Name of material
140
- :param color: Color string in html for like #FF0000 (red)
141
- :param opacity: Makes material transparent (0.0) or non-transparent (1.0)
142
- """
143
- self.materialList[name].color = color
144
- self.materialList[name].opacity = opacity
145
-
146
- def addPort(self, name="", portStart=[0.0, 0.0, 0.0], width=0.0, height=0.0, R=50.0, direction=em.ZAX, excitationAmplitude:float=0.0, geometryObject:em._emerge.geometry.GeoObject=None, portNumber:int=-1):
147
- self.portList[name] = {}
148
- self.portList[name]['portStart'] = portStart
149
- self.portList[name]['width'] = width
150
- self.portList[name]['height'] = height
151
- self.portList[name]['R'] = R
152
- self.portList[name]['direction'] = direction
153
- self.portList[name]['excitationAmplitude'] = excitationAmplitude
154
- self.portList[name]['object'] = geometryObject
155
- self.portList[name]['portNumber'] = self._generatedPortIndex if portNumber == -1 else portNumber
156
-
157
- if portNumber == -1:
158
- self._generatedPortIndex += 1
159
-
160
- def getPort(self, name):
161
- return self.portList[name]
162
-
163
- def getPortByNumber(self, portNumber):
164
- resultPortObj = None
165
- for portObj in self.portList:
166
- if portNumber == portObj['portNumber']:
167
- resultPortObj = portObj
168
- return resultPortObj
169
-
170
- def getPortNumber(self, name):
171
- for portObj in self.portList:
172
- if portObj['portNumber'] == name:
173
- return portObj['portNumber']
174
-
175
- def setPortAsLumpedPort(self, name, searchObjectName=""):
176
- portObj = self.getPort(name)
177
-
178
- #
179
- # Port object can be splitted since there was fragmentation operation in EMerge
180
- #
181
- portGeometryObjectList = self.getAllObjectByName(name if searchObjectName == "" else searchObjectName)
182
- for geometryObj in portGeometryObjectList:
183
- if portObj['excitationAmplitude'] > 0.0:
184
- self.simulationObj.mw.bc.LumpedPort(
185
- geometryObj,
186
- port_number=portObj['portNumber'],
187
- width=portObj['width'],
188
- height=portObj['height'],
189
- direction=portObj['direction'],
190
- Z0=portObj['R'],
191
- power=portObj['excitationAmplitude']
192
- )
193
- else:
194
- self.simulationObj.mw.bc.LumpedPort(
195
- geometryObj,
196
- port_number=portObj['portNumber'],
197
- width=portObj['width'],
198
- height=portObj['height'],
199
- direction=portObj['direction'],
200
- Z0=portObj['R']
201
- )
202
-
203
- self._temporaryInternalPortIndex += 1
204
-
205
- def plotSParamUsingPortName(self, sourcePortName, targetPortName, dblim=[-40, 0], plotSmithChart=False):
206
- sourcePortNumber = self.getPortNumber(sourcePortName)
207
- targetPortNumber = self.getPortNumber(targetPortName)
208
-
209
- self.plotSParamUsingPortNumbers(sourcePortNumber, targetPortNumber, dblim, plotSmithChart)
210
-
211
- def plotSParamUsingPortNumbers(self, sourcePortNumber, targetPortNumber, dblim=[-40, 0], xunit="GHz", plotSmithChart=False, plotInterpolatedPoints:int=-1, plotS11=False):
212
- simulationResult = self.simulationObj.data.mw
213
-
214
- freqs = simulationResult.scalar.grid.freq
215
- fmin = freqs.min()
216
- fmax = freqs.max()
217
-
218
- if plotInterpolatedPoints > 0:
219
- #
220
- # Add points into frequency axis and interpolate computed S param over these points it makes graph line smooth but it can provide wrong result!!!
221
- #
222
- freq_dense = np.linspace(fmin, fmax, plotInterpolatedPoints)
223
- S_data = simulationResult.scalar.grid.model_S(sourcePortNumber, targetPortNumber, freq_dense)
224
- plotLabel = f'S{sourcePortNumber}{targetPortNumber}'
225
- plot_sp(freq_dense, S_data, labels=plotLabel, dblim=dblim)
226
- else:
227
- S21_data = simulationResult.scalar.grid.S(sourcePortNumber, targetPortNumber)
228
- S11_data = simulationResult.scalar.grid.S(sourcePortNumber, sourcePortNumber)
229
- plotLabel_S11 = f'S{sourcePortNumber}{sourcePortNumber}'
230
- plotLabel_S21 = f'S{targetPortNumber}{sourcePortNumber}'
231
- if plotS11:
232
- plot_sp(freqs, [S11_data, S21_data], labels=[plotLabel_S11, plotLabel_S21], dblim=dblim, xunit=xunit)
233
- else:
234
- plot_sp(freqs, [S21_data], labels=[plotLabel_S21], dblim=dblim, xunit=xunit)
235
-
236
- if plotSmithChart:
237
- smith(S_data, f=freq_dense, labels=plotLabel) # smith chart
238
-
239
- def addObjectToView(self, nameOrList: str | list, opacity:float=0.1):
240
- objectList = []
241
- if type(nameOrList) == str:
242
- objectList = self.getAllObjectByName(nameOrList)
243
- if type(nameOrList) == list:
244
- for oneName in nameOrList:
245
- objectList.extend(self.getAllObjectByName(oneName))
246
-
247
- for geoObject in objectList:
248
- self.simulationObj.display.add_object(geoObject, opacity=opacity)