DeepSDFStruct 1.7.2__py3-none-any.whl

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 (165) hide show
  1. DeepSDFStruct/SDF.py +1734 -0
  2. DeepSDFStruct/__init__.py +66 -0
  3. DeepSDFStruct/deep_sdf/__init__.py +69 -0
  4. DeepSDFStruct/deep_sdf/create_screenshots_from_plyfiles.py +65 -0
  5. DeepSDFStruct/deep_sdf/data.py +258 -0
  6. DeepSDFStruct/deep_sdf/metrics/__init__.py +14 -0
  7. DeepSDFStruct/deep_sdf/metrics/mesh_to_analytical.py +24 -0
  8. DeepSDFStruct/deep_sdf/models.py +197 -0
  9. DeepSDFStruct/deep_sdf/networks/__init__.py +8 -0
  10. DeepSDFStruct/deep_sdf/networks/analytic_round_cross.py +105 -0
  11. DeepSDFStruct/deep_sdf/networks/deep_sdf_decoder.py +155 -0
  12. DeepSDFStruct/deep_sdf/networks/hierarchical_deep_sdf_decoder.py +139 -0
  13. DeepSDFStruct/deep_sdf/networks/hierarchical_positional_sdf_decoder.py +190 -0
  14. DeepSDFStruct/deep_sdf/networks/resnet_positional_sdf_decoder.py +108 -0
  15. DeepSDFStruct/deep_sdf/nn_utils.py +70 -0
  16. DeepSDFStruct/deep_sdf/plotting.py +143 -0
  17. DeepSDFStruct/deep_sdf/reconstruction.py +165 -0
  18. DeepSDFStruct/deep_sdf/training.py +840 -0
  19. DeepSDFStruct/deep_sdf/workspace.py +363 -0
  20. DeepSDFStruct/design_of_experiments.py +235 -0
  21. DeepSDFStruct/flexicubes/__init__.py +15 -0
  22. DeepSDFStruct/flexicubes/flexicubes.py +1005 -0
  23. DeepSDFStruct/flexicubes/tables.py +2338 -0
  24. DeepSDFStruct/flexisquares/__init__.py +15 -0
  25. DeepSDFStruct/flexisquares/flexisquares.py +967 -0
  26. DeepSDFStruct/flexisquares/tables.py +63 -0
  27. DeepSDFStruct/lattice_structure.py +254 -0
  28. DeepSDFStruct/local_shapes.py +226 -0
  29. DeepSDFStruct/mesh.py +1184 -0
  30. DeepSDFStruct/optimization.py +357 -0
  31. DeepSDFStruct/parametrization.py +215 -0
  32. DeepSDFStruct/pretrained_models.py +128 -0
  33. DeepSDFStruct/sampling.py +670 -0
  34. DeepSDFStruct/sdf_operations.py +990 -0
  35. DeepSDFStruct/sdf_primitives.py +1416 -0
  36. DeepSDFStruct/splinepy_unitcells/__init__.py +35 -0
  37. DeepSDFStruct/splinepy_unitcells/chi_3D.py +1617 -0
  38. DeepSDFStruct/splinepy_unitcells/cross_lattice.py +165 -0
  39. DeepSDFStruct/splinepy_unitcells/double_lattice_extruded.py +298 -0
  40. DeepSDFStruct/splinepy_unitcells/snappy_3d.py +544 -0
  41. DeepSDFStruct/torch_spline.py +446 -0
  42. DeepSDFStruct/trained_models/analytic_round_cross/LatentCodes/latest.pth +0 -0
  43. DeepSDFStruct/trained_models/analytic_round_cross/ModelParameters/latest.pth +0 -0
  44. DeepSDFStruct/trained_models/analytic_round_cross/OptimizerParameters/latest.pth +0 -0
  45. DeepSDFStruct/trained_models/analytic_round_cross/specs.json +45 -0
  46. DeepSDFStruct/trained_models/chi_and_cross/LatentCodes/latest.pth +0 -0
  47. DeepSDFStruct/trained_models/chi_and_cross/Logs.pth +0 -0
  48. DeepSDFStruct/trained_models/chi_and_cross/ModelParameters/latest.pth +0 -0
  49. DeepSDFStruct/trained_models/chi_and_cross/OptimizerParameters/latest.pth +0 -0
  50. DeepSDFStruct/trained_models/chi_and_cross/specs.json +76 -0
  51. DeepSDFStruct/trained_models/primitives/LatentCodes/latest.pth +0 -0
  52. DeepSDFStruct/trained_models/primitives/ModelParameters/latest.pth +0 -0
  53. DeepSDFStruct/trained_models/primitives/OptimizerParameters/latest.pth +0 -0
  54. DeepSDFStruct/trained_models/primitives/specs.json +46 -0
  55. DeepSDFStruct/trained_models/primitives_2d/LatentCodes/latest.pth +0 -0
  56. DeepSDFStruct/trained_models/primitives_2d/ModelParameters/latest.pth +0 -0
  57. DeepSDFStruct/trained_models/primitives_2d/OptimizerParameters/latest.pth +0 -0
  58. DeepSDFStruct/trained_models/primitives_2d/specs.json +46 -0
  59. DeepSDFStruct/trained_models/round_cross/LatentCodes/latest.pth +0 -0
  60. DeepSDFStruct/trained_models/round_cross/Logs.pth +0 -0
  61. DeepSDFStruct/trained_models/round_cross/ModelParameters/latest.pth +0 -0
  62. DeepSDFStruct/trained_models/round_cross/OptimizerParameters/latest.pth +0 -0
  63. DeepSDFStruct/trained_models/round_cross/specs.json +45 -0
  64. DeepSDFStruct/trained_models/test_experiment/LatentCodes/latent_code_data_map.json +106 -0
  65. DeepSDFStruct/trained_models/test_experiment/specs.json +45 -0
  66. DeepSDFStruct/trained_models/test_experiment/training_summary.json +10 -0
  67. DeepSDFStruct/trained_models/test_experiment_hierarchical/LatentCodes/latent_code_data_map.json +106 -0
  68. DeepSDFStruct/trained_models/test_experiment_hierarchical/specs.json +45 -0
  69. DeepSDFStruct/trained_models/test_experiment_hierarchical/training_summary.json +10 -0
  70. DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latent_code_data_map.json +5006 -0
  71. DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latest.pth +0 -0
  72. DeepSDFStruct/trained_models/test_experiment_homogenization/Logs.png +0 -0
  73. DeepSDFStruct/trained_models/test_experiment_homogenization/ModelParameters/latest.pth +0 -0
  74. DeepSDFStruct/trained_models/test_experiment_homogenization/OptimizerParameters/latest.pth +0 -0
  75. DeepSDFStruct/trained_models/test_experiment_homogenization/specs.json +83 -0
  76. DeepSDFStruct/trained_models/test_experiment_homogenization/training_summary.json +10 -0
  77. DeepSDFStruct/utils.py +96 -0
  78. DeepSDFStruct/visualization/latent_viewer.py +211 -0
  79. DeepSDFStruct/visualization/latent_viewer_cli.py +8 -0
  80. benchmarks/generate_sdf_showcase.py +622 -0
  81. benchmarks/sdf_from_mesh_benchmark.py +52 -0
  82. benchmarks/sdf_from_mesh_comparison.md +33 -0
  83. benchmarks/sdf_showcase/operations/boolean/difference_sphere_box.png +0 -0
  84. benchmarks/sdf_showcase/operations/boolean/union_sphere_box.png +0 -0
  85. benchmarks/sdf_showcase/operations/transformations/circular_array_sphere.png +0 -0
  86. benchmarks/sdf_showcase/operations/transformations/dilate_sphere.png +0 -0
  87. benchmarks/sdf_showcase/operations/transformations/mirror_sphere.png +0 -0
  88. benchmarks/sdf_showcase/operations/transformations/repeat_sphere.png +0 -0
  89. benchmarks/sdf_showcase/operations/transformations/revolve_circle.png +0 -0
  90. benchmarks/sdf_showcase/operations/transformations/shell_sphere.png +0 -0
  91. benchmarks/sdf_showcase/operations/transformations/twist_torus.png +0 -0
  92. benchmarks/sdf_showcase/primitives/2D/circle.png +0 -0
  93. benchmarks/sdf_showcase/primitives/2D/equilateral_triangle.png +0 -0
  94. benchmarks/sdf_showcase/primitives/2D/hexagon.png +0 -0
  95. benchmarks/sdf_showcase/primitives/2D/polygon_pentagon.png +0 -0
  96. benchmarks/sdf_showcase/primitives/2D/rectangle.png +0 -0
  97. benchmarks/sdf_showcase/primitives/2D/rounded_rectangle.png +0 -0
  98. benchmarks/sdf_showcase/primitives/3D/box.png +0 -0
  99. benchmarks/sdf_showcase/primitives/3D/capped_cone.png +0 -0
  100. benchmarks/sdf_showcase/primitives/3D/capped_cylinder.png +0 -0
  101. benchmarks/sdf_showcase/primitives/3D/capsule.png +0 -0
  102. benchmarks/sdf_showcase/primitives/3D/cone.png +0 -0
  103. benchmarks/sdf_showcase/primitives/3D/corner_spheres.png +0 -0
  104. benchmarks/sdf_showcase/primitives/3D/cross_ms.png +0 -0
  105. benchmarks/sdf_showcase/primitives/3D/cylinder.png +0 -0
  106. benchmarks/sdf_showcase/primitives/3D/dodecahedron.png +0 -0
  107. benchmarks/sdf_showcase/primitives/3D/ellipsoid.png +0 -0
  108. benchmarks/sdf_showcase/primitives/3D/icosahedron.png +0 -0
  109. benchmarks/sdf_showcase/primitives/3D/octahedron.png +0 -0
  110. benchmarks/sdf_showcase/primitives/3D/pyramid.png +0 -0
  111. benchmarks/sdf_showcase/primitives/3D/rounded_box.png +0 -0
  112. benchmarks/sdf_showcase/primitives/3D/rounded_cone.png +0 -0
  113. benchmarks/sdf_showcase/primitives/3D/rounded_cylinder.png +0 -0
  114. benchmarks/sdf_showcase/primitives/3D/sphere.png +0 -0
  115. benchmarks/sdf_showcase/primitives/3D/tetrahedron.png +0 -0
  116. benchmarks/sdf_showcase/primitives/3D/torus.png +0 -0
  117. benchmarks/sdf_showcase/primitives/3D/wireframe_box.png +0 -0
  118. deepsdfstruct-1.7.2.dist-info/METADATA +44 -0
  119. deepsdfstruct-1.7.2.dist-info/RECORD +165 -0
  120. deepsdfstruct-1.7.2.dist-info/WHEEL +5 -0
  121. deepsdfstruct-1.7.2.dist-info/licenses/LICENSE +208 -0
  122. deepsdfstruct-1.7.2.dist-info/licenses/NOTICE +67 -0
  123. deepsdfstruct-1.7.2.dist-info/scm_file_list.json +178 -0
  124. deepsdfstruct-1.7.2.dist-info/scm_version.json +8 -0
  125. deepsdfstruct-1.7.2.dist-info/top_level.txt +4 -0
  126. docs/Makefile +20 -0
  127. docs/api_reference.rst +6 -0
  128. docs/conf.py +60 -0
  129. docs/index.rst +136 -0
  130. docs/make.bat +35 -0
  131. docs/qr_code_github.png +0 -0
  132. docs/qr_code_paper.png +0 -0
  133. docs/readme_images/example_output_01.png +0 -0
  134. docs/readme_images/example_output_02.png +0 -0
  135. docs/readme_images/example_output_03.png +0 -0
  136. docs/readme_images/example_output_04.png +0 -0
  137. tests/data/chairs/1005.obj +282 -0
  138. tests/data/chairs/1024.obj +222 -0
  139. tests/data/chairs/README.md +1 -0
  140. tests/data/circular_balls_test_case.stl +0 -0
  141. tests/data/cone.stl +1794 -0
  142. tests/data/example_disconnectd_mesh.inp +1784 -0
  143. tests/data/example_line_mesh.vtk +0 -0
  144. tests/data/stanford_bunny.stl +0 -0
  145. tests/data/sweep_test_case.stl +0 -0
  146. tests/test_DOE.py +49 -0
  147. tests/test_elongate_sdf.py +195 -0
  148. tests/test_flexisquares.py +187 -0
  149. tests/test_generate_dataset.py +76 -0
  150. tests/test_lattice_evaluation.py +137 -0
  151. tests/test_mesh_export.py +137 -0
  152. tests/test_mesh_functions.py +49 -0
  153. tests/test_networks.py +71 -0
  154. tests/test_pretrained_models.py +40 -0
  155. tests/test_reconstruction.py +101 -0
  156. tests/test_sdf_from_mesh.py +52 -0
  157. tests/test_sdf_functions.py +117 -0
  158. tests/test_sdf_primitives.py +668 -0
  159. tests/test_sdf_trimesh_comparison.py +482 -0
  160. tests/test_splinepy_unitcells.py +64 -0
  161. tests/test_structural_optimization.py +178 -0
  162. tests/test_torch_spline.py +216 -0
  163. tests/test_train_model.py +110 -0
  164. tests/test_training_data_ids.py +41 -0
  165. tests/tmp_outputs/temp_file.txt +0 -0
Binary file
Binary file
Binary file
tests/test_DOE.py ADDED
@@ -0,0 +1,49 @@
1
+ from DeepSDFStruct.design_of_experiments import run_experiment, ExperimentSpecifications
2
+ from huggingface_hub import snapshot_download
3
+ from huggingface_hub.utils import HfHubHTTPError
4
+ import time
5
+
6
+
7
+ def snapshot_download_with_retry(*args, max_retries=3, **kwargs):
8
+ """Download with retry on 429 rate limit errors."""
9
+ for attempt in range(max_retries):
10
+ try:
11
+ return snapshot_download(*args, **kwargs)
12
+ except HfHubHTTPError as e:
13
+ if e.response and e.response.status_code == 429:
14
+ if attempt < max_retries - 1:
15
+ wait_time = 60 * (attempt + 1)
16
+ print(
17
+ f"Rate limited (429). Waiting {wait_time}s before retry {attempt + 2}/{max_retries}"
18
+ )
19
+ time.sleep(wait_time)
20
+ else:
21
+ raise
22
+ raise
23
+
24
+
25
+ def test_DOE():
26
+ data_dir = snapshot_download_with_retry(
27
+ "mkofler/lattice_structure_unit_cells",
28
+ repo_type="dataset",
29
+ revision="b80339abc071df77ff81e8abc19ad4856d96ddbd",
30
+ )
31
+ specs = ExperimentSpecifications(
32
+ "DeepSDFStruct/trained_models/test_experiment/specs.json"
33
+ )
34
+ # Define hyperparameter grid / overrides
35
+ tuning_params = [
36
+ {"NetworkSpecs": {"latent_in": [2], "dropout_prob": 0.2}},
37
+ {"NetworkSpecs": {"latent_in": [3], "dropout_prob": 0.3}},
38
+ {"NetworkSpecs": {"latent_in": [4], "dropout_prob": 0.4}},
39
+ ]
40
+
41
+ # Run experiments
42
+ for i, overrides in enumerate(tuning_params):
43
+ specs.update(overrides)
44
+ exp_name = f"run_{i}"
45
+ run_experiment(exp_name, data_dir, specs=specs, device="cpu")
46
+
47
+
48
+ if __name__ == "__main__":
49
+ test_DOE()
@@ -0,0 +1,195 @@
1
+ """
2
+ Test ElongateSDF and wrapper SDFs with SmoothUnion operations.
3
+
4
+ This test ensures that wrapper SDFs properly preserve tensor shapes
5
+ when used in combination with SmoothUnionSDF and other composite operations.
6
+ """
7
+
8
+ import pytest
9
+ import torch
10
+ from DeepSDFStruct.SDF import SmoothUnionSDF, UnionSDF
11
+ from DeepSDFStruct.sdf_operations import (
12
+ ElongateSDF,
13
+ TwistSDF,
14
+ BendLinearSDF,
15
+ BendRadialSDF,
16
+ DilateSDF,
17
+ ErodeSDF,
18
+ ShellSDF,
19
+ MirrorSDF,
20
+ )
21
+ from DeepSDFStruct.sdf_primitives import SphereSDF, RoundedBoxSDF, CapsuleSDF
22
+
23
+
24
+ class TestElongateSDF:
25
+ """Test ElongateSDF wrapper."""
26
+
27
+ def test_basic_elongate(self):
28
+ """Test basic ElongateSDF functionality."""
29
+ sphere = SphereSDF(center=[0, 0, 0], radius=0.5)
30
+ elongated = ElongateSDF(sphere, size=[0.5, 0.5, 0.5])
31
+
32
+ queries = torch.randn(100, 3)
33
+ result = elongated(queries)
34
+
35
+ assert result.shape == (100, 1), f"Expected shape (100, 1), got {result.shape}"
36
+ assert torch.isfinite(result).all(), "Result contains NaN or Inf"
37
+
38
+ def test_elongate_with_smooth_union(self):
39
+ """Test ElongateSDF in SmoothUnion - reproduces the original bug."""
40
+ sphere = SphereSDF(center=[0, 0, 0], radius=0.3)
41
+ elongated_sphere = ElongateSDF(sphere, size=[0.5, 0.2, 0.2])
42
+ capsule = CapsuleSDF(point_a=[1.0, 0, 0], point_b=[1.5, 0, 0], radius=0.2)
43
+ box = RoundedBoxSDF(center=[-1.0, 0, 0], extents=[0.5, 0.5, 0.5], radius=0.1)
44
+
45
+ smooth_union = SmoothUnionSDF(elongated_sphere, capsule, box, k=0.1)
46
+
47
+ queries = torch.randn(1000, 3)
48
+ result = smooth_union(queries)
49
+
50
+ assert result.shape == (
51
+ 1000,
52
+ 1,
53
+ ), f"Expected shape (1000, 1), got {result.shape}"
54
+ assert torch.isfinite(result).all(), "Result contains NaN or Inf"
55
+
56
+ def test_elongate_shape_preservation(self):
57
+ """Test that ElongateSDF preserves input tensor shape."""
58
+ sphere = SphereSDF(center=[0, 0, 0], radius=0.5)
59
+ elongated = ElongateSDF(sphere, size=[0.3, 0.3, 0.3])
60
+
61
+ for n_points in [1, 10, 100, 1000, 41616]:
62
+ queries = torch.randn(n_points, 3)
63
+ result = elongated(queries)
64
+ assert result.shape == (
65
+ n_points,
66
+ 1,
67
+ ), f"Shape mismatch for {n_points} points: expected ({n_points}, 1), got {result.shape}"
68
+
69
+ def test_elongate_gradient_flow(self):
70
+ """Test that gradients flow through ElongateSDF."""
71
+ sphere = SphereSDF(center=[0, 0, 0], radius=0.5)
72
+ elongated = ElongateSDF(sphere, size=[0.3, 0.3, 0.3])
73
+
74
+ queries = torch.randn(100, 3, requires_grad=True)
75
+ result = elongated(queries)
76
+ loss = result.sum()
77
+ loss.backward()
78
+
79
+ assert queries.grad is not None, "Gradients not computed"
80
+ assert torch.isfinite(queries.grad).all(), "Gradients contain NaN or Inf"
81
+
82
+
83
+ class TestWrapperSDFsWithSmoothUnion:
84
+ """Test various wrapper SDFs with SmoothUnion."""
85
+
86
+ def test_twist_sdf_smooth_union(self):
87
+ """Test TwistSDF in SmoothUnion."""
88
+ sphere = SphereSDF(center=[0, 0, 0], radius=0.5)
89
+ twisted = TwistSDF(sphere, k=0.5)
90
+ box = RoundedBoxSDF(center=[1.0, 0, 0], extents=[0.3, 0.3, 0.3], radius=0.05)
91
+
92
+ smooth_union = SmoothUnionSDF(twisted, box, k=0.1)
93
+
94
+ queries = torch.randn(500, 3)
95
+ result = smooth_union(queries)
96
+
97
+ assert result.shape == (500, 1), f"Expected shape (500, 1), got {result.shape}"
98
+ assert torch.isfinite(result).all(), "Result contains NaN or Inf"
99
+
100
+ def test_bend_linear_sdf_smooth_union(self):
101
+ """Test BendLinearSDF in SmoothUnion."""
102
+ sphere = SphereSDF(center=[0, 0, 0], radius=0.3)
103
+ bent = BendLinearSDF(sphere, p0=[0, 0, 0], p1=[0, 0, 1], v=[0.1, 0, 0])
104
+ capsule = CapsuleSDF(point_a=[0.5, 0, 0], point_b=[1.0, 0, 0], radius=0.2)
105
+
106
+ smooth_union = SmoothUnionSDF(bent, capsule, k=0.1)
107
+
108
+ queries = torch.randn(500, 3)
109
+ result = smooth_union(queries)
110
+
111
+ assert result.shape == (500, 1), f"Expected shape (500, 1), got {result.shape}"
112
+ assert torch.isfinite(result).all(), "Result contains NaN or Inf"
113
+
114
+ def test_dilate_sdf_smooth_union(self):
115
+ """Test DilateSDF in SmoothUnion."""
116
+ sphere = SphereSDF(center=[0, 0, 0], radius=0.3)
117
+ dilated = DilateSDF(sphere, r=0.1)
118
+ box = RoundedBoxSDF(center=[0.8, 0, 0], extents=[0.3, 0.3, 0.3], radius=0.05)
119
+
120
+ smooth_union = SmoothUnionSDF(dilated, box, k=0.1)
121
+
122
+ queries = torch.randn(500, 3)
123
+ result = smooth_union(queries)
124
+
125
+ assert result.shape == (500, 1), f"Expected shape (500, 1), got {result.shape}"
126
+ assert torch.isfinite(result).all(), "Result contains NaN or Inf"
127
+
128
+ def test_mirror_sdf_smooth_union(self):
129
+ """Test MirrorSDF in SmoothUnion."""
130
+ sphere = SphereSDF(center=[0.5, 0.5, 0], radius=0.3)
131
+ mirrored = MirrorSDF(sphere, plane_point=[0, 0, 0], plane_normal=[1, 0, 0])
132
+ capsule = CapsuleSDF(point_a=[0, 0, 0], point_b=[0, 1, 0], radius=0.2)
133
+
134
+ smooth_union = SmoothUnionSDF(mirrored, capsule, k=0.1)
135
+
136
+ queries = torch.randn(500, 3)
137
+ result = smooth_union(queries)
138
+
139
+ assert result.shape == (500, 1), f"Expected shape (500, 1), got {result.shape}"
140
+ assert torch.isfinite(result).all(), "Result contains NaN or Inf"
141
+
142
+
143
+ class TestFormula1CarScenario:
144
+ """Test the specific scenario from the bug report - Formula 1 car chassis."""
145
+
146
+ def test_main_chassis_smooth_union(self):
147
+ """Test MainChassis configuration from bug report."""
148
+ rounded_box = RoundedBoxSDF(
149
+ center=[0, 0, 0.4], extents=[2.5, 0.8, 0.5], radius=0.15
150
+ )
151
+ elongated = ElongateSDF(rounded_box, size=[2.5, 0.4, 0.3])
152
+
153
+ capsule = CapsuleSDF(point_a=[1.5, 0, 0.3], point_b=[2.5, 0, 0.3], radius=0.25)
154
+
155
+ rounded_box2 = RoundedBoxSDF(
156
+ center=[-1.5, 0, 0.5], extents=[1.5, 0.7, 0.4], radius=0.1
157
+ )
158
+
159
+ smooth_union = SmoothUnionSDF(elongated, capsule, rounded_box2, k=0.1)
160
+
161
+ queries = torch.randn(41616, 3)
162
+ result = smooth_union(queries)
163
+
164
+ assert result.shape == (
165
+ 41616,
166
+ 1,
167
+ ), f"Expected shape (41616, 1), got {result.shape}"
168
+ assert torch.isfinite(result).all(), "Result contains NaN or Inf"
169
+
170
+ def test_side_pod_smooth_union(self):
171
+ """Test SidePod configuration from bug report."""
172
+ rounded_box = RoundedBoxSDF(
173
+ center=[-0.5, 0.55, 0.35], extents=[1.2, 0.35, 0.35], radius=0.12
174
+ )
175
+
176
+ from DeepSDFStruct.sdf_primitives import CappedConeSDF
177
+
178
+ capped_cone = CappedConeSDF(
179
+ point_a=[0.5, 0.55, 0.35], point_b=[-1.2, 0.55, 0.35], ra=0.25, rb=0.15
180
+ )
181
+
182
+ smooth_union = SmoothUnionSDF(rounded_box, capped_cone, k=0.15)
183
+
184
+ queries = torch.randn(1000, 3)
185
+ result = smooth_union(queries)
186
+
187
+ assert result.shape == (
188
+ 1000,
189
+ 1,
190
+ ), f"Expected shape (1000, 1), got {result.shape}"
191
+ assert torch.isfinite(result).all(), "Result contains NaN or Inf"
192
+
193
+
194
+ if __name__ == "__main__":
195
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,187 @@
1
+ from DeepSDFStruct.sdf_primitives import CircleSDF, RectangleSDF
2
+ from DeepSDFStruct.flexisquares.flexisquares import FlexiSquares
3
+ from DeepSDFStruct.torch_spline import TorchSpline
4
+ from DeepSDFStruct.pretrained_models import get_model, PretrainedModels
5
+ from DeepSDFStruct.SDF import (
6
+ SDFfromDeepSDF,
7
+ CappedBorderSDF,
8
+ SDFfromLineMesh,
9
+ DifferenceSDF,
10
+ )
11
+ from DeepSDFStruct.lattice_structure import LatticeSDFStruct
12
+ from DeepSDFStruct.parametrization import Constant
13
+ from DeepSDFStruct.mesh import create_2D_mesh
14
+ import numpy as np
15
+ import gustaf as gus
16
+ import splinepy
17
+ import torch
18
+
19
+
20
+ def test_flexisquares_linemesh_diag_and_straight():
21
+ linemesh = gus.Edges(
22
+ vertices=np.array([[-1, -1], [1, 1], [1, -1]]), edges=np.array([[0, 1], [1, 2]])
23
+ )
24
+ thickness = 0.4
25
+ rectangle = RectangleSDF(center=[0, 0], extents=[3.9, 3.9])
26
+ sdf_linemesh = SDFfromLineMesh(linemesh, thickness=thickness)
27
+ sdf = DifferenceSDF(rectangle, sdf_linemesh)
28
+ bounds = torch.tensor([[-2.0, -2.0], [2.0, 2.0]])
29
+ mesh, _ = create_2D_mesh(sdf, 10, mesh_type="surface", bounds=bounds)
30
+ mesh.export("linemesh.vtk")
31
+
32
+
33
+ def test_flexisquares_linemesh_diag1():
34
+ linemesh = gus.Edges(
35
+ vertices=np.array([[-2, -2], [2, 2]]), edges=np.array([[0, 1]])
36
+ )
37
+ thickness = 0.4
38
+ rectangle = RectangleSDF(center=[0, 0], extents=[3.9, 3.9])
39
+ sdf_linemesh = SDFfromLineMesh(linemesh, thickness=thickness)
40
+ sdf = DifferenceSDF(rectangle, sdf_linemesh)
41
+ bounds = torch.tensor([[-2.0, -2.0], [2.0, 2.0]])
42
+ mesh, _ = create_2D_mesh(sdf, 2, mesh_type="surface", bounds=bounds)
43
+ torch.testing.assert_close(
44
+ mesh.vertices,
45
+ torch.tensor(
46
+ [
47
+ [-1.2913, 0.1414],
48
+ [-1.2913, 1.2913],
49
+ [0.1414, -1.2913],
50
+ [-0.5253, -0.5253],
51
+ [0.5253, 0.5253],
52
+ [-0.1414, 1.2913],
53
+ [1.2913, -1.2913],
54
+ [1.2913, -0.1414],
55
+ [-0.5486, 0.5486],
56
+ [0.5486, -0.5486],
57
+ ]
58
+ ),
59
+ rtol=1e-4,
60
+ atol=1e-4,
61
+ )
62
+
63
+
64
+ def test_flexisquares_linemesh_diag2():
65
+ linemesh = gus.Edges(
66
+ vertices=np.array([[-2, 2], [2, -2]]), edges=np.array([[0, 1]])
67
+ )
68
+ thickness = 0.4
69
+ rectangle = RectangleSDF(center=[0, 0], extents=[3.9, 3.9])
70
+ sdf_linemesh = SDFfromLineMesh(linemesh, thickness=thickness)
71
+ sdf = DifferenceSDF(rectangle, sdf_linemesh)
72
+ bounds = torch.tensor([[-2.0, -2.0], [2.0, 2.0]])
73
+ mesh, _ = create_2D_mesh(sdf, 2, mesh_type="surface", bounds=bounds)
74
+ torch.testing.assert_close(
75
+ mesh.vertices,
76
+ torch.tensor(
77
+ [
78
+ [-1.2913, -1.2913],
79
+ [-1.2913, -0.1414],
80
+ [-0.1414, -1.2913],
81
+ [-0.5253, 0.5253],
82
+ [0.5253, -0.5253],
83
+ [0.1414, 1.2913],
84
+ [1.2913, 0.1414],
85
+ [1.2913, 1.2913],
86
+ [-0.5486, -0.5486],
87
+ [0.5486, 0.5486],
88
+ ]
89
+ ),
90
+ rtol=1e-4,
91
+ atol=1e-4,
92
+ )
93
+
94
+
95
+ def test_flexisquares_simple():
96
+ circle_sdf = CircleSDF(center=(0.0, 0.0), radius=0.5)
97
+ bounds = torch.tensor([[-1, -1], [1, 1]])
98
+ fsq = FlexiSquares(device=bounds.device)
99
+ verts, square_idx = fsq.construct_voxel_grid([10, 10], bounds=bounds)
100
+ # this function is useful to plot the squares (voxels)
101
+ # for square in square_idx:
102
+ # indices = square.index_select(0, torch.tensor([0, 1, 3, 2, 0]))
103
+ # ax.plot(verts[indices][:, 0], verts[indices][:, 1], color="black", lw=0.3)
104
+ scalar_field = circle_sdf(verts)
105
+ vd_surf, edges, L_dev = fsq(verts, scalar_field, square_idx)
106
+ vd_surf_and_interior, faces, L_dev = fsq(
107
+ verts, scalar_field, square_idx, output_tetmesh=True
108
+ )
109
+ # check that the first vertices are the boundary vertices:
110
+ assert (
111
+ vd_surf.shape[0] == 20
112
+ ), f"Expected 20 surface vertices, got {vd_surf.shape[0]}"
113
+ torch.testing.assert_close(vd_surf, vd_surf_and_interior[:20])
114
+ assert (
115
+ circle_sdf(vd_surf).abs() < 0.05
116
+ ).all(), "Signed Distance Function not close to zero at border vertices"
117
+ assert (circle_sdf(vd_surf) < 0.05).all(), "Some interior nodes are positive"
118
+ mesh, _ = create_2D_mesh(circle_sdf, 40, mesh_type="surface", bounds=bounds)
119
+ try:
120
+ mesh, _ = create_2D_mesh(circle_sdf, 40, mesh_type="surface")
121
+ raise ValueError(
122
+ "This test should raise an error, because the bounds are not correct"
123
+ )
124
+ except RuntimeError as rError:
125
+ print(f"The test correctly raised the runtimeerror {rError} ✅️")
126
+ mesh.export("tests/tmp_outputs/circle_mesh.vtk")
127
+
128
+
129
+ def test_flexisquares_lattice_struct():
130
+ model = get_model(PretrainedModels.AnalyticRoundCross)
131
+ sdf = SDFfromDeepSDF(model)
132
+
133
+ # Define a spline-based deformation field
134
+ deformation_spline = TorchSpline(
135
+ splinepy.helpme.create.box(2, 1).bspline, device=model.device
136
+ )
137
+
138
+ # Create the lattice structure with deformation and microtile
139
+ lattice_struct = CappedBorderSDF(
140
+ LatticeSDFStruct(
141
+ tiling=(6, 3),
142
+ microtile=sdf.to2D(axes=[0, 1], offset=0.5),
143
+ parametrization=Constant([0.5], device=model.device),
144
+ )
145
+ )
146
+ bounds = torch.tensor([[-0.05, -0.05], [1.05, 1.05]], device=model.device)
147
+ fsq = FlexiSquares(device=model.device)
148
+ verts, square_idx = fsq.construct_voxel_grid([60, 30], bounds=bounds)
149
+ scalar_field = lattice_struct(verts) + 0.01
150
+
151
+ vd_surf, edges, L_dev = fsq(verts, scalar_field, square_idx)
152
+
153
+ vd_surf_and_interior, faces, L_dev = fsq(
154
+ verts, scalar_field, square_idx, output_tetmesh=True
155
+ )
156
+
157
+ assert (lattice_struct(vd_surf) < 0.1).all(), "Some interior nodes are positive"
158
+
159
+ # check if deformation spline works on extracted mesh
160
+ vd = deformation_spline(vd_surf_and_interior)
161
+ vd_surf_and_interior_manual = vd_surf_and_interior
162
+ vd_surf_and_interior_manual[:, 0] = vd_surf_and_interior[:, 0] * 2
163
+ torch.testing.assert_close(vd, vd_surf_and_interior_manual)
164
+
165
+
166
+ def test_near_zero_epsilon():
167
+ """
168
+ Test edge case where scalar values are exactly 0 or very close to it.
169
+ This often causes division by zero in interpolation if not handled.
170
+ """
171
+ fsq = FlexiSquares(device="cpu")
172
+ verts = torch.tensor([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]])
173
+ square_idx = torch.tensor([[0, 1, 2, 3]])
174
+
175
+ # Exactly zero on two corners
176
+ scalar_field = torch.tensor([0.0, 0.0, 1.0, 1.0])
177
+
178
+ vd, edges, _ = fsq(verts, scalar_field, square_idx)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ test_flexisquares_linemesh_diag_and_straight()
183
+ test_flexisquares_linemesh_diag1()
184
+ test_flexisquares_linemesh_diag2()
185
+ test_flexisquares_simple()
186
+ test_near_zero_epsilon()
187
+ test_flexisquares_lattice_struct()
@@ -0,0 +1,76 @@
1
+ import numpy as np
2
+ from DeepSDFStruct.sampling import SDFSampler, augment_by_FFD
3
+ from DeepSDFStruct.splinepy_unitcells.chi_3D import Chi3D
4
+ from DeepSDFStruct.splinepy_unitcells.cross_lattice import CrossLattice
5
+ import splinepy
6
+ import multiprocessing as mp
7
+ import pytest
8
+ import pathlib
9
+
10
+
11
+ @pytest.fixture(scope="session", autouse=True)
12
+ def set_mp_start_method():
13
+ mp.set_start_method("spawn", force=True)
14
+
15
+
16
+ def test_generate_dataset_from_obj_files():
17
+ outdir = "./training_data"
18
+ splitdir = "./training_data/splits"
19
+ dataset_name = "furniture"
20
+
21
+ sdf_sampler = SDFSampler(outdir, splitdir, dataset_name, overwrite_existing=True)
22
+ meshs = sdf_sampler.get_meshs_from_folder(
23
+ foldername="./tests/data/chairs", mesh_type="obj"
24
+ )
25
+ meshs_augment = augment_by_FFD(meshs)
26
+ sdf_sampler.add_class(meshs_augment, class_name="chairs")
27
+
28
+ sdf_sampler.process_geometries(
29
+ sampling_strategy="uniform",
30
+ add_surface_samples=True,
31
+ also_save_vtk=True,
32
+ n_samples=10,
33
+ )
34
+
35
+ sdf_sampler.write_json("chairs_train.json")
36
+
37
+
38
+ @pytest.mark.parametrize("n_workers", [0, 1])
39
+ def test_generate_dataset(n_workers, tmp_path):
40
+ outdir = tmp_path / "training_data"
41
+ splitdir = tmp_path / "training_data/splits"
42
+ dataset_name = "microstructure"
43
+
44
+ sdf_sampler = SDFSampler(outdir, splitdir, dataset_name, overwrite_existing=True)
45
+
46
+ t_start = 0.1 * np.sqrt(2) / 2
47
+ t_end = 0.15 * np.sqrt(2) / 2
48
+ crosslattice_tiles = []
49
+ for t in np.linspace(t_start, t_end, 3):
50
+ tile, _ = CrossLattice().create_tile(np.array([[t]]), make3D=True)
51
+ crosslattice_tiles.append(splinepy.Multipatch(tile))
52
+
53
+ chi = Chi3D()
54
+ chi_tiles = []
55
+
56
+ for phi in np.linspace(0, -np.pi / 6, 2):
57
+ for x2 in np.linspace(-0.1, 0.2, 2):
58
+ t = 0.1
59
+ x1 = 0.2
60
+ r = 0.5 * t
61
+ tile, _ = chi.create_tile(np.array([[phi, t, x1, x2, r]] * 5))
62
+ chi_tiles.append(splinepy.Multipatch(tile))
63
+
64
+ sdf_sampler.add_class(chi_tiles, class_name="Chi3D_center", n_faces=3)
65
+ sdf_sampler.add_class(crosslattice_tiles, class_name="CrossLattice", n_faces=3)
66
+
67
+ sdf_sampler.process_geometries(
68
+ sampling_strategy="uniform", n_workers=n_workers, n_samples=10
69
+ )
70
+
71
+ sdf_sampler.write_json("chi_and_cross.json")
72
+
73
+
74
+ if __name__ == "__main__":
75
+ test_generate_dataset_from_obj_files()
76
+ test_generate_dataset(n_workers=1, tmp_path=pathlib.Path("."))
@@ -0,0 +1,137 @@
1
+ from DeepSDFStruct.local_shapes import LocalShapesSDF
2
+ from DeepSDFStruct.pretrained_models import get_model, PretrainedModels
3
+ from DeepSDFStruct.SDF import SDFfromDeepSDF, _cap_outside_of_unitcube
4
+ from DeepSDFStruct.lattice_structure import LatticeSDFStruct
5
+ from DeepSDFStruct.parametrization import Constant
6
+ from DeepSDFStruct.torch_spline import TorchSpline
7
+ import splinepy
8
+ import torch
9
+ import matplotlib.pyplot as plt
10
+
11
+
12
+ def test_deepsdf_evaluation():
13
+ # Load a pretrained DeepSDF model
14
+ model = get_model(PretrainedModels.ChiAndCross)
15
+ sdf = SDFfromDeepSDF(model)
16
+
17
+ # Set the latent vector and visualize a slice of the SDF
18
+ sdf.set_latent_vec(
19
+ torch.tensor([0.3, 0.3], dtype=torch.float32, device=model.device)
20
+ )
21
+
22
+ out = sdf(
23
+ torch.tensor(
24
+ [[0, 0, 0], [0, 1, 0], [0.5, 0.5, 0.5]],
25
+ dtype=torch.float32,
26
+ device=model.device,
27
+ )
28
+ )
29
+ print(out)
30
+
31
+
32
+ def test_deepsdf_lattice_evaluation():
33
+ # Load a pretrained DeepSDF model
34
+ model = get_model(PretrainedModels.AnalyticRoundCross)
35
+ sdf = SDFfromDeepSDF(model)
36
+
37
+ # Define a spline-based deformation field
38
+ deformation_spline = TorchSpline(
39
+ splinepy.helpme.create.box(1, 1, 1).bspline, device=model.device
40
+ )
41
+
42
+ # Create the lattice structure with deformation and microtile
43
+ lattice_struct = LatticeSDFStruct(
44
+ tiling=(1, 1, 1),
45
+ microtile=sdf,
46
+ parametrization=Constant([0.5], device=model.device),
47
+ )
48
+
49
+ out = lattice_struct(
50
+ torch.tensor(
51
+ [[0, 0, 0], [0, 1, 0], [0.5, 0.5, 0.5]],
52
+ dtype=torch.float32,
53
+ device=model.device,
54
+ )
55
+ )
56
+ fig, axs = plt.subplots(1, 3)
57
+ lattice_struct.plot_slice(
58
+ origin=(0, 0.5, 0),
59
+ normal=(0, 1, 0),
60
+ ax=axs[0],
61
+ show_zero_level=True,
62
+ res=(300, 300),
63
+ deformation_function=deformation_spline,
64
+ )
65
+ print(out)
66
+
67
+
68
+ def test_lattice_local_shapes_comparison():
69
+ # Load a pretrained DeepSDF model
70
+ model = get_model(PretrainedModels.AnalyticRoundCross)
71
+ sdf = SDFfromDeepSDF(model)
72
+
73
+ # Create the lattice structure with deformation and microtile
74
+ lattice_struct = LatticeSDFStruct(
75
+ tiling=(2, 1, 1),
76
+ microtile=sdf,
77
+ parametrization=Constant([0.5], device=model.device),
78
+ )
79
+
80
+ test_input_param_domain = torch.tensor(
81
+ [[0, 0, 0], [0, 1, 0], [0.5, 0.5, 0.5]],
82
+ dtype=torch.float32,
83
+ device=model.device,
84
+ )
85
+
86
+ out_latt = lattice_struct(test_input_param_domain)
87
+
88
+ local_shape_sdf = LocalShapesSDF(
89
+ tiling=(2, 1, 1),
90
+ unit_cell=sdf,
91
+ parametrization=Constant([0.5], device=model.device),
92
+ bounds=torch.tensor([[0, 0, 0], [2, 1, 1]]),
93
+ )
94
+
95
+ out_ls = local_shape_sdf(test_input_param_domain)
96
+
97
+ torch.testing.assert_close(out_latt, out_ls)
98
+
99
+
100
+ def test_cap_outside_unitcube():
101
+
102
+ samples = torch.tensor(
103
+ [
104
+ [1.1, 0.0, 0.0],
105
+ [-0.1, 0.0, 0.0],
106
+ [0.0, 1.1, 0.0],
107
+ [0.0, -0.1, 0.0],
108
+ [0.0, 0.0, 1.1],
109
+ [0.0, 0.0, -0.1],
110
+ [0.1, 0.1, 0.1],
111
+ [0.9, 0.9, 0.9],
112
+ [0.0, 0.0, 0.0],
113
+ ],
114
+ dtype=torch.float32,
115
+ )
116
+ # true = inside, false = outside
117
+ labels = [False, False, False, False, False, False, True, True]
118
+
119
+ # Initial SDF values: all negative (e.g., "inside")
120
+ sdf_values = torch.tensor([[-0.5]] * samples.shape[0], dtype=torch.float32)
121
+
122
+ capped_sdf = _cap_outside_of_unitcube(samples, sdf_values.clone())
123
+
124
+ # Check that the SDF is capped (increased) for points outside the cube
125
+ for sample, sdf_value, label in zip(samples, capped_sdf, labels):
126
+ if sdf_value < 1e-10:
127
+ continue
128
+ assertion = (sdf_value < 0) == label
129
+ assert assertion, f"Incorrect capping on valid sample {sample}"
130
+ assert capped_sdf[-1] < 1e10, "Point on the boundary is wrong"
131
+
132
+
133
+ if __name__ == "__main__":
134
+ test_deepsdf_evaluation()
135
+ test_lattice_local_shapes_comparison()
136
+ test_cap_outside_unitcube()
137
+ test_deepsdf_lattice_evaluation()