39
39
import json
40
40
import logging
41
41
import numpy as np
42
+ import warnings
42
43
44
+ from collections .abc import Iterable , Sequence
43
45
from math import degrees , pi , radians , isclose
44
46
from typing import Any , overload , TypeAlias , TYPE_CHECKING
45
47
46
- from collections .abc import Iterable , Sequence
47
-
48
48
import OCP .TopAbs as TopAbs_ShapeEnum
49
49
50
50
from OCP .Bnd import Bnd_Box , Bnd_OBB
@@ -1252,6 +1252,68 @@ def __repr__(self) -> str:
1252
1252
return f"Color{ str (tuple (self ))} "
1253
1253
1254
1254
1255
+ class GeomEncoder (json .JSONEncoder ):
1256
+ """
1257
+ A JSON encoder for build123d geometry objects.
1258
+
1259
+ This class extends ``json.JSONEncoder`` to provide custom serialization for
1260
+ geometry objects such as Axis, Color, Location, Plane, and Vector. It converts
1261
+ each geometry object into a dictionary containing exactly one key that identifies
1262
+ the geometry type (e.g. ``"Axis"``, ``"Vector"``, etc.), paired with a tuple or
1263
+ list that represents the underlying data. Any other object types are handled by
1264
+ the standard encoder.
1265
+
1266
+ The inverse decoding is performed by the ``geometry_hook`` static method, which
1267
+ expects the dictionary to have precisely one key from the known geometry types.
1268
+ It then uses a class registry (``CLASS_REGISTRY``) to look up and instantiate
1269
+ the appropriate class with the provided values.
1270
+
1271
+ **Usage Example**::
1272
+
1273
+ import json
1274
+
1275
+ # Suppose we have some geometry objects:
1276
+ axis = Axis(position=(0, 0, 0), direction=(1, 0, 0))
1277
+ vector = Vector(0.0, 1.0, 2.0)
1278
+
1279
+ data = {
1280
+ "my_axis": axis,
1281
+ "my_vector": vector
1282
+ }
1283
+
1284
+ # Encode them to JSON:
1285
+ encoded_data = json.dumps(data, cls=GeomEncoder, indent=4)
1286
+
1287
+ # Decode them back:
1288
+ decoded_data = json.loads(encoded_data, object_hook=GeomEncoder.geometry_hook)
1289
+
1290
+ """
1291
+
1292
+ def default (self , obj ):
1293
+ """Return a JSON-serializable representation of a known geometry object."""
1294
+ if isinstance (obj , Axis ):
1295
+ return {"Axis" : (tuple (obj .position ), tuple (obj .direction ))}
1296
+ elif isinstance (obj , Color ):
1297
+ return {"Color" : obj .to_tuple ()}
1298
+ if isinstance (obj , Location ):
1299
+ return {"Location" : obj .to_tuple ()}
1300
+ elif isinstance (obj , Plane ):
1301
+ return {"Plane" : (tuple (obj .origin ), tuple (obj .x_dir ), tuple (obj .z_dir ))}
1302
+ elif isinstance (obj , Vector ):
1303
+ return {"Vector" : tuple (obj )}
1304
+ else :
1305
+ # Let the base class default method raise the TypeError
1306
+ return super ().default (obj )
1307
+
1308
+ @staticmethod
1309
+ def geometry_hook (json_dict ):
1310
+ """Convert dictionaries back into geometry objects for decoding."""
1311
+ if len (json_dict .items ()) != 1 :
1312
+ raise ValueError (f"Invalid geometry json object { json_dict } " )
1313
+ for key , value in json_dict .items ():
1314
+ return CLASS_REGISTRY [key ](* value )
1315
+
1316
+
1255
1317
class Location :
1256
1318
"""Location in 3D space. Depending on usage can be absolute or relative.
1257
1319
@@ -1714,6 +1776,7 @@ class LocationEncoder(json.JSONEncoder):
1714
1776
1715
1777
def default (self , o : Location ) -> dict :
1716
1778
"""Return a serializable object"""
1779
+ warnings .warn ("Use GeomEncoder instead" , DeprecationWarning , stacklevel = 2 )
1717
1780
if not isinstance (o , Location ):
1718
1781
raise TypeError ("Only applies to Location objects" )
1719
1782
return {"Location" : o .to_tuple ()}
@@ -1725,6 +1788,7 @@ def location_hook(obj) -> dict:
1725
1788
Example:
1726
1789
read_json = json.load(infile, object_hook=LocationEncoder.location_hook)
1727
1790
"""
1791
+ warnings .warn ("Use GeomEncoder instead" , DeprecationWarning , stacklevel = 2 )
1728
1792
if "Location" in obj :
1729
1793
obj = Location (* [[float (f ) for f in v ] for v in obj ["Location" ]])
1730
1794
return obj
@@ -2890,6 +2954,15 @@ def intersect(self, *args, **kwargs):
2890
2954
return shape .intersect (self )
2891
2955
2892
2956
2957
+ CLASS_REGISTRY = {
2958
+ "Axis" : Axis ,
2959
+ "Color" : Color ,
2960
+ "Location" : Location ,
2961
+ "Plane" : Plane ,
2962
+ "Vector" : Vector ,
2963
+ }
2964
+
2965
+
2893
2966
def to_align_offset (
2894
2967
min_point : VectorLike ,
2895
2968
max_point : VectorLike ,
0 commit comments