1515from ..Util .Observer import Observable
1616from .SideLengthCalculator import AverageSideLengthCalculator
1717from .Vector import Vector
18- from .Iolets import ObservableListOfIolets , IoletLoader
18+ # from .Iolets import ObservableListOfIolets, IoletLoader
19+ from .Iolets import ObservableListOfIolets , IoletLoader , Inlet , Outlet # ← added Inlet, Outlet
20+
21+ import types # required for dynamic module creation
22+
23+ class FakeUnpickler (pickle .Unpickler ):
24+ def __init__ (self , * args , ** kwargs ):
25+ super ().__init__ (* args , ** kwargs )
26+ self ._HST = types .ModuleType ("HemeLbSetupTool" )
27+ self ._classes = {}
28+
29+ def _get_or_make_mod (self , moduleName ):
30+ parts = moduleName .split ("." )
31+ hst = parts .pop (0 )
32+ assert hst == "HemeLbSetupTool"
33+ full = hst
34+ cur = self ._HST
35+ while parts :
36+ name = parts .pop (0 )
37+ if not hasattr (cur , name ):
38+ mod = types .ModuleType (f"{ full } .{ name } " )
39+ mod .__package__ = full
40+ full = mod .__name__
41+ setattr (cur , name , mod )
42+ cur = getattr (cur , name )
43+ return cur
44+
45+ def _get_or_make_class (self , mod , className ):
46+ try :
47+ return getattr (mod , className )
48+ except AttributeError :
49+ def __up__ (this ):
50+ return this # no-op upgrade
51+ fake = type (className , (object ,), {"__module__" : mod .__name__ , "__up__" : __up__ })
52+ setattr (mod , className , fake )
53+ return fake
54+
55+ def find_class (self , moduleName , className ):
56+ if moduleName .startswith ("HemeLbSetupTool" ):
57+ mod = self ._get_or_make_mod (moduleName )
58+ return self ._get_or_make_class (mod , className )
59+ return super ().find_class (moduleName , className )
60+
1961
2062
2163class LengthUnit (Observable ):
@@ -205,9 +247,45 @@ def LoadProfileV2(self, filename):
205247
206248 def LoadProfileV1 (self , filename ):
207249 with open (filename , "rb" ) as f :
208- restored = pickle .Unpickler (f , fix_imports = True ).load ()
209- restored ._ResetPaths (filename )
210- self .CloneFrom (restored )
250+ # restored = pickle.Unpickler(f, fix_imports=True).load()
251+ restored_fake = FakeUnpickler (f ).load ()
252+ halfway = restored_fake .__up__ () # convert fake object to a dict-like profile object
253+
254+ # Manually assign fields instead of using CloneFrom(), to avoid constructor issues
255+ for attr in Profile ._Args :
256+ val = getattr (halfway , attr , None )
257+
258+ if attr == 'SeedPoint' and val is not None :
259+ # Upgrade legacy vector to real Vector instance
260+ self .SeedPoint = Vector (val .x , val .y , val .z )
261+
262+ elif attr == 'Iolets' and val is not None :
263+ # Upgrade list of fake Inlet/Outlet objects to real ones
264+ real_iolets = ObservableListOfIolets ()
265+ for io in val :
266+ # Choose correct type (Inlet or Outlet)
267+ inlet_or_outlet = Inlet () if getattr (io , 'Name' , '' ).startswith ("Inlet" ) else Outlet ()
268+
269+ # Set required fields
270+ inlet_or_outlet .Name = getattr (io , 'Name' , None )
271+ inlet_or_outlet .Radius = getattr (io , 'Radius' , None )
272+ inlet_or_outlet .Centre = Vector (io .Centre .x , io .Centre .y , io .Centre .z )
273+ inlet_or_outlet .Normal = Vector (io .Normal .x , io .Normal .y , io .Normal .z )
274+ inlet_or_outlet .Pressure = Vector (io .Pressure .x , io .Pressure .y , io .Pressure .z )
275+
276+ real_iolets .append (inlet_or_outlet )
277+
278+ self .Iolets = real_iolets
279+
280+ elif val is not None :
281+ # Default assignment for all other attributes
282+ setattr (self , attr , val )
283+
284+ # Adjust file paths to be relative to the profile file
285+ self ._ResetPaths (filename )
286+
287+ # restored._ResetPaths(filename)
288+ # self.CloneFrom(restored)
211289 return
212290
213291 def _ResetPaths (self , filename ):
@@ -280,4 +358,4 @@ def IsFileValid(path, ext=None, exists=None):
280358 if ending != ext :
281359 return False
282360 pass
283- return True
361+ return True
0 commit comments