diff --git a/Code/tests/pythontests/resources/poiseuille_flow_test.pr2 b/Code/tests/pythontests/resources/poiseuille_flow_test.pr2 new file mode 100644 index 000000000..e62c69bac --- /dev/null +++ b/Code/tests/pythontests/resources/poiseuille_flow_test.pr2 @@ -0,0 +1,42 @@ +DurationSeconds: 0x1.4000000000000p+2 +Iolets: +- Centre: + x: 0x0.0p+0 + y: 0x0.0p+0 + z: -0x1.e000000000000p+4 + Name: Inlet1 + Normal: + x: 0x0.0p+0 + y: 0x0.0p+0 + z: 0x1.0000000000000p+0 + Pressure: + x: 0x1.0000000000000p+4 + y: 0x0.0p+0 + z: 0x0.0p+0 + Radius: 0x1.8000000000000p-1 + Type: Inlet +- Centre: + x: 0x0.0p+0 + y: 0x0.0p+0 + z: 0x1.e000000000000p+4 + Name: Outlet1 + Normal: + x: 0x0.0p+0 + y: 0x0.0p+0 + z: -0x1.0000000000000p+0 + Pressure: + x: 0x0.0p+0 + y: 0x0.0p+0 + z: 0x0.0p+0 + Radius: 0x1.8000000000000p-1 + Type: Outlet +OutputGeometryFile: poiseuille_flow_test.gmy +OutputXmlFile: poiseuille_flow_test.xml +SeedPoint: + x: -0x1.00da93eaa8180p-1 + y: -0x1.156ab416a2440p-6 + z: -0x1.f88444ad1efccp+3 +StlFile: poiseuille_flow_test.stl +StlFileUnitId: 1 +TimeStepSeconds: 0x1.a36e2eb1c432dp-14 +VoxelSize: 0x1.568f2c0000000p-3 diff --git a/geometry-tool/HlbGmyTool/Model/Profile.py b/geometry-tool/HlbGmyTool/Model/Profile.py index 5db173fef..7379ff867 100644 --- a/geometry-tool/HlbGmyTool/Model/Profile.py +++ b/geometry-tool/HlbGmyTool/Model/Profile.py @@ -15,7 +15,49 @@ from ..Util.Observer import Observable from .SideLengthCalculator import AverageSideLengthCalculator from .Vector import Vector -from .Iolets import ObservableListOfIolets, IoletLoader +# from .Iolets import ObservableListOfIolets, IoletLoader +from .Iolets import ObservableListOfIolets, IoletLoader, Inlet, Outlet # ← added Inlet, Outlet + +import types # required for dynamic module creation + +class FakeUnpickler(pickle.Unpickler): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._HST = types.ModuleType("HemeLbSetupTool") + self._classes = {} + + def _get_or_make_mod(self, moduleName): + parts = moduleName.split(".") + hst = parts.pop(0) + assert hst == "HemeLbSetupTool" + full = hst + cur = self._HST + while parts: + name = parts.pop(0) + if not hasattr(cur, name): + mod = types.ModuleType(f"{full}.{name}") + mod.__package__ = full + full = mod.__name__ + setattr(cur, name, mod) + cur = getattr(cur, name) + return cur + + def _get_or_make_class(self, mod, className): + try: + return getattr(mod, className) + except AttributeError: + def __up__(this): + return this # no-op upgrade + fake = type(className, (object,), {"__module__": mod.__name__, "__up__": __up__}) + setattr(mod, className, fake) + return fake + + def find_class(self, moduleName, className): + if moduleName.startswith("HemeLbSetupTool"): + mod = self._get_or_make_mod(moduleName) + return self._get_or_make_class(mod, className) + return super().find_class(moduleName, className) + class LengthUnit(Observable): @@ -205,9 +247,45 @@ def LoadProfileV2(self, filename): def LoadProfileV1(self, filename): with open(filename, "rb") as f: - restored = pickle.Unpickler(f, fix_imports=True).load() - restored._ResetPaths(filename) - self.CloneFrom(restored) + # restored = pickle.Unpickler(f, fix_imports=True).load() + restored_fake = FakeUnpickler(f).load() + halfway = restored_fake.__up__() # convert fake object to a dict-like profile object + + # Manually assign fields instead of using CloneFrom(), to avoid constructor issues + for attr in Profile._Args: + val = getattr(halfway, attr, None) + + if attr == 'SeedPoint' and val is not None: + # Upgrade legacy vector to real Vector instance + self.SeedPoint = Vector(val.x, val.y, val.z) + + elif attr == 'Iolets' and val is not None: + # Upgrade list of fake Inlet/Outlet objects to real ones + real_iolets = ObservableListOfIolets() + for io in val: + # Choose correct type (Inlet or Outlet) + inlet_or_outlet = Inlet() if getattr(io, 'Name', '').startswith("Inlet") else Outlet() + + # Set required fields + inlet_or_outlet.Name = getattr(io, 'Name', None) + inlet_or_outlet.Radius = getattr(io, 'Radius', None) + inlet_or_outlet.Centre = Vector(io.Centre.x, io.Centre.y, io.Centre.z) + inlet_or_outlet.Normal = Vector(io.Normal.x, io.Normal.y, io.Normal.z) + inlet_or_outlet.Pressure = Vector(io.Pressure.x, io.Pressure.y, io.Pressure.z) + + real_iolets.append(inlet_or_outlet) + + self.Iolets = real_iolets + + elif val is not None: + # Default assignment for all other attributes + setattr(self, attr, val) + + # Adjust file paths to be relative to the profile file + self._ResetPaths(filename) + + # restored._ResetPaths(filename) + # self.CloneFrom(restored) return def _ResetPaths(self, filename): @@ -280,4 +358,4 @@ def IsFileValid(path, ext=None, exists=None): if ending != ext: return False pass - return True + return True \ No newline at end of file