metafold 0.12.dev5__tar.gz → 0.12.dev7__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.
- {metafold-0.12.dev5 → metafold-0.12.dev7}/PKG-INFO +2 -2
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/materials.py +94 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/simulation/compression_simulation.py +5 -4
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/simulation/run_experiment.py +9 -1
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold.egg-info/PKG-INFO +2 -2
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold.egg-info/SOURCES.txt +1 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold.egg-info/requires.txt +1 -1
- {metafold-0.12.dev5 → metafold-0.12.dev7}/pyproject.toml +2 -2
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_compression_simulation.py +37 -13
- metafold-0.12.dev7/tests/test_materials.py +281 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_run_experiment.py +33 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/LICENSE +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/README.md +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/__init__.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/api.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/assets.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/auth.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/client.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/exceptions.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/jobs.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/projects.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/simulation/__init__.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/simulation/compression_experiment.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/utils.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold/workflows.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold.egg-info/dependency_links.txt +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/metafold.egg-info/top_level.txt +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/setup.cfg +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_assets.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_compession_experiment.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_jobs.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_projects.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_shear_simulation.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_utils.py +0 -0
- {metafold-0.12.dev5 → metafold-0.12.dev7}/tests/test_workflows.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metafold
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.dev7
|
|
4
4
|
Summary: Metafold SDK for Python
|
|
5
5
|
Author-email: Metafold 3D <info@metafold3d.com>
|
|
6
6
|
License: Copyright 2024 Metafold 3D
|
|
@@ -38,7 +38,7 @@ Requires-Dist: dotenv>=0.9.9; extra == "simulation"
|
|
|
38
38
|
Requires-Dist: pandas>=2.3.3; extra == "simulation"
|
|
39
39
|
Requires-Dist: plyfile>=1.1.3; extra == "simulation"
|
|
40
40
|
Requires-Dist: pyyaml>=6.0.3; extra == "simulation"
|
|
41
|
-
Requires-Dist: simulation-configurator==0.1.
|
|
41
|
+
Requires-Dist: simulation-configurator==0.1.2; extra == "simulation"
|
|
42
42
|
Requires-Dist: tables>=3.11.1; extra == "simulation"
|
|
43
43
|
Dynamic: license-file
|
|
44
44
|
|
|
@@ -32,6 +32,94 @@ class RigidParams(ParamsBase):
|
|
|
32
32
|
return {"shear_modulus": self.shear_modulus, "bulk_modulus": self.bulk_modulus}
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
@dataclass
|
|
36
|
+
class HypoElasticParams(ParamsBase):
|
|
37
|
+
G: float
|
|
38
|
+
K: float
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_type(self):
|
|
42
|
+
return "hypo_elastic"
|
|
43
|
+
|
|
44
|
+
def to_dict(self):
|
|
45
|
+
return {"G": self.G, "K": self.K}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ViscoTransIsoHyperParams(ParamsBase):
|
|
50
|
+
bulk_modulus: float
|
|
51
|
+
c1: float
|
|
52
|
+
c2: float
|
|
53
|
+
c3: float
|
|
54
|
+
c4: float
|
|
55
|
+
c5: float
|
|
56
|
+
fiber_stretch: float
|
|
57
|
+
direction_of_symm: List[float]
|
|
58
|
+
failure_option: int
|
|
59
|
+
max_fiber_strain: float
|
|
60
|
+
max_matrix_strain: float
|
|
61
|
+
y1: float
|
|
62
|
+
y2: float
|
|
63
|
+
y3: float
|
|
64
|
+
y4: float
|
|
65
|
+
y5: float
|
|
66
|
+
y6: float
|
|
67
|
+
t1: float
|
|
68
|
+
t2: float
|
|
69
|
+
t3: float
|
|
70
|
+
t4: float
|
|
71
|
+
t5: float
|
|
72
|
+
t6: float
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def get_type(cls):
|
|
76
|
+
return "visco_trans_iso_hyper"
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_dict(cls, d: dict) -> "ViscoTransIsoHyperParams":
|
|
80
|
+
dos = d["direction_of_symm"]
|
|
81
|
+
direction = [float(x) for x in dos.split()] if isinstance(dos, str) else [float(x) for x in dos]
|
|
82
|
+
return cls(
|
|
83
|
+
bulk_modulus=d["bulk_modulus"],
|
|
84
|
+
c1=d["c1"], c2=d["c2"], c3=d["c3"], c4=d["c4"], c5=d["c5"],
|
|
85
|
+
fiber_stretch=d["fiber_stretch"],
|
|
86
|
+
direction_of_symm=direction,
|
|
87
|
+
failure_option=d["failure_option"],
|
|
88
|
+
max_fiber_strain=d["max_fiber_strain"],
|
|
89
|
+
max_matrix_strain=d["max_matrix_strain"],
|
|
90
|
+
y1=d["y1"], y2=d["y2"], y3=d["y3"], y4=d["y4"], y5=d["y5"], y6=d["y6"],
|
|
91
|
+
t1=d["t1"], t2=d["t2"], t3=d["t3"], t4=d["t4"], t5=d["t5"], t6=d["t6"],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def to_dict(self):
|
|
95
|
+
return {
|
|
96
|
+
"bulk_modulus": float(self.bulk_modulus),
|
|
97
|
+
"c1": float(self.c1),
|
|
98
|
+
"c2": float(self.c2),
|
|
99
|
+
"c3": float(self.c3),
|
|
100
|
+
"c4": float(self.c4),
|
|
101
|
+
"c5": float(self.c5),
|
|
102
|
+
"fiber_stretch": float(self.fiber_stretch),
|
|
103
|
+
# Emit as a numeric list so simulation_configurator serialises it correctly
|
|
104
|
+
"direction_of_symm": [float(x) for x in self.direction_of_symm],
|
|
105
|
+
"failure_option": int(self.failure_option),
|
|
106
|
+
"max_fiber_strain": float(self.max_fiber_strain),
|
|
107
|
+
"max_matrix_strain": float(self.max_matrix_strain),
|
|
108
|
+
"y1": float(self.y1),
|
|
109
|
+
"y2": float(self.y2),
|
|
110
|
+
"y3": float(self.y3),
|
|
111
|
+
"y4": float(self.y4),
|
|
112
|
+
"y5": float(self.y5),
|
|
113
|
+
"y6": float(self.y6),
|
|
114
|
+
"t1": float(self.t1),
|
|
115
|
+
"t2": float(self.t2),
|
|
116
|
+
"t3": float(self.t3),
|
|
117
|
+
"t4": float(self.t4),
|
|
118
|
+
"t5": float(self.t5),
|
|
119
|
+
"t6": float(self.t6),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
35
123
|
@dataclass
|
|
36
124
|
class CompMooneyRivlinParams(ParamsBase):
|
|
37
125
|
he_constant_1: float
|
|
@@ -220,12 +308,16 @@ class ConstitutiveModel:
|
|
|
220
308
|
UCNHParams,
|
|
221
309
|
CompNeoHookParams,
|
|
222
310
|
ElasticPlasticParams,
|
|
311
|
+
HypoElasticParams,
|
|
312
|
+
ViscoTransIsoHyperParams,
|
|
223
313
|
]
|
|
224
314
|
|
|
225
315
|
def to_dict(self):
|
|
226
316
|
return {"type": self.params.get_type(), "params": self.params.to_dict()}
|
|
227
317
|
|
|
228
318
|
|
|
319
|
+
|
|
320
|
+
|
|
229
321
|
@dataclass
|
|
230
322
|
class Material:
|
|
231
323
|
density: float
|
|
@@ -257,6 +349,8 @@ class Material:
|
|
|
257
349
|
MaxwellWeichertParams,
|
|
258
350
|
CompNeoHookParams,
|
|
259
351
|
ElasticPlasticParams,
|
|
352
|
+
HypoElasticParams,
|
|
353
|
+
ViscoTransIsoHyperParams,
|
|
260
354
|
]
|
|
261
355
|
PARAMS_CLASSES = {c.get_type(): c for c in params_classes}
|
|
262
356
|
cm = d["constitutive_model"]
|
|
@@ -625,10 +625,11 @@ class CompressionSimulation:
|
|
|
625
625
|
auth_domain=auth_domain,
|
|
626
626
|
base_url=base_url,
|
|
627
627
|
)
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
628
|
+
# Quote the project_name string for multi-word name searchess
|
|
629
|
+
existing = [
|
|
630
|
+
p for p in client.projects.list(q=f'name:"{project_name}"')
|
|
631
|
+
if p.name == project_name # ensure full match
|
|
632
|
+
]
|
|
632
633
|
if existing:
|
|
633
634
|
project_id = existing[0].id
|
|
634
635
|
if cancel_existing_workflows:
|
|
@@ -29,7 +29,9 @@ JSON manifest format
|
|
|
29
29
|
]
|
|
30
30
|
},
|
|
31
31
|
{"type": "piston_box", "shape_parameters": {"min": [...], "max": [...]}},
|
|
32
|
-
|
|
32
|
+
# piston parts take an optional "material" (preset key or inline dict);
|
|
33
|
+
# omit to use DEFAULT_PISTON_MATERIAL
|
|
34
|
+
{"type": "piston_mesh", "file": "piston.ply", "velocity": [...], "material": "default_piston_material"},
|
|
33
35
|
{
|
|
34
36
|
"type": "mesh",
|
|
35
37
|
"name": "midsole",
|
|
@@ -177,6 +179,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
177
179
|
kwargs["velocity"] = entry["velocity"]
|
|
178
180
|
if "shape_parameters" in entry:
|
|
179
181
|
kwargs["shape_parameters"] = entry["shape_parameters"]
|
|
182
|
+
if "material" in entry:
|
|
183
|
+
kwargs["material"] = _resolve_material(entry["material"])
|
|
180
184
|
parts.append(ExperimentPistonCylinder(**kwargs))
|
|
181
185
|
|
|
182
186
|
elif part_type == "piston_box":
|
|
@@ -185,12 +189,16 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
|
|
|
185
189
|
kwargs["velocity"] = entry["velocity"]
|
|
186
190
|
if "shape_parameters" in entry:
|
|
187
191
|
kwargs["shape_parameters"] = entry["shape_parameters"]
|
|
192
|
+
if "material" in entry:
|
|
193
|
+
kwargs["material"] = _resolve_material(entry["material"])
|
|
188
194
|
parts.append(ExperimentPistonBox(**kwargs))
|
|
189
195
|
|
|
190
196
|
elif part_type == "piston_mesh":
|
|
191
197
|
kwargs = {"filename": entry["file"]}
|
|
192
198
|
if "velocity" in entry:
|
|
193
199
|
kwargs["velocity"] = entry["velocity"]
|
|
200
|
+
if "material" in entry:
|
|
201
|
+
kwargs["material"] = _resolve_material(entry["material"])
|
|
194
202
|
parts.append(ExperimentPistonMesh(**kwargs))
|
|
195
203
|
|
|
196
204
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metafold
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.dev7
|
|
4
4
|
Summary: Metafold SDK for Python
|
|
5
5
|
Author-email: Metafold 3D <info@metafold3d.com>
|
|
6
6
|
License: Copyright 2024 Metafold 3D
|
|
@@ -38,7 +38,7 @@ Requires-Dist: dotenv>=0.9.9; extra == "simulation"
|
|
|
38
38
|
Requires-Dist: pandas>=2.3.3; extra == "simulation"
|
|
39
39
|
Requires-Dist: plyfile>=1.1.3; extra == "simulation"
|
|
40
40
|
Requires-Dist: pyyaml>=6.0.3; extra == "simulation"
|
|
41
|
-
Requires-Dist: simulation-configurator==0.1.
|
|
41
|
+
Requires-Dist: simulation-configurator==0.1.2; extra == "simulation"
|
|
42
42
|
Requires-Dist: tables>=3.11.1; extra == "simulation"
|
|
43
43
|
Dynamic: license-file
|
|
44
44
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "metafold"
|
|
7
|
-
version = "0.12.
|
|
7
|
+
version = "0.12.dev7"
|
|
8
8
|
authors = [
|
|
9
9
|
{name = "Metafold 3D", email = "info@metafold3d.com"},
|
|
10
10
|
]
|
|
@@ -38,7 +38,7 @@ simulation = [
|
|
|
38
38
|
"pandas>=2.3.3",
|
|
39
39
|
"plyfile>=1.1.3",
|
|
40
40
|
"pyyaml>=6.0.3",
|
|
41
|
-
"simulation-configurator==0.1.
|
|
41
|
+
"simulation-configurator==0.1.2",
|
|
42
42
|
"tables>=3.11.1",
|
|
43
43
|
]
|
|
44
44
|
|
|
@@ -890,6 +890,7 @@ class TestSetupClient:
|
|
|
890
890
|
# projects.list() finds an existing match.
|
|
891
891
|
existing = MagicMock()
|
|
892
892
|
existing.id = "existing-pid"
|
|
893
|
+
existing.name = "my_project"
|
|
893
894
|
first_client = MagicMock()
|
|
894
895
|
first_client.projects.list.return_value = [existing]
|
|
895
896
|
patched_client_class.side_effect = [first_client, MagicMock()]
|
|
@@ -901,12 +902,34 @@ class TestSetupClient:
|
|
|
901
902
|
)
|
|
902
903
|
|
|
903
904
|
assert sim.project_id == "existing-pid"
|
|
904
|
-
first_client.projects.list.assert_called_once_with(q=
|
|
905
|
+
first_client.projects.list.assert_called_once_with(q='name:"my_project"')
|
|
905
906
|
first_client.projects.create.assert_not_called()
|
|
906
907
|
# MetafoldClient called twice — once without project_id, once with
|
|
907
908
|
assert patched_client_class.call_count == 2
|
|
908
909
|
assert patched_client_class.call_args_list[1].kwargs["project_id"] == "existing-pid"
|
|
909
910
|
|
|
911
|
+
def test_multiword_name_is_quoted_and_reused(
|
|
912
|
+
self, ply_folder, basic_parts, tmp_path, patched_client_class
|
|
913
|
+
):
|
|
914
|
+
# Names with spaces must be quoted in the search query, otherwise the
|
|
915
|
+
# server's parser errors and a duplicate project gets created.
|
|
916
|
+
existing = MagicMock()
|
|
917
|
+
existing.id = "existing-pid"
|
|
918
|
+
existing.name = "grasshopper test 3"
|
|
919
|
+
first_client = MagicMock()
|
|
920
|
+
first_client.projects.list.return_value = [existing]
|
|
921
|
+
patched_client_class.side_effect = [first_client, MagicMock()]
|
|
922
|
+
|
|
923
|
+
sim = self._build_sim(
|
|
924
|
+
ply_folder, basic_parts, tmp_path,
|
|
925
|
+
project_name="grasshopper test 3",
|
|
926
|
+
create_project_if_needed=True,
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
assert sim.project_id == "existing-pid"
|
|
930
|
+
first_client.projects.list.assert_called_once_with(q='name:"grasshopper test 3"')
|
|
931
|
+
first_client.projects.create.assert_not_called()
|
|
932
|
+
|
|
910
933
|
def test_new_project_created_when_name_not_found(
|
|
911
934
|
self, ply_folder, basic_parts, tmp_path, patched_client_class
|
|
912
935
|
):
|
|
@@ -949,25 +972,24 @@ class TestSetupClient:
|
|
|
949
972
|
assert first_client.projects.create.call_args.args[0].startswith("experiment_")
|
|
950
973
|
assert sim.project_id == "auto-pid"
|
|
951
974
|
|
|
952
|
-
def
|
|
975
|
+
def test_http_error_during_list_propagates_and_does_not_create(
|
|
953
976
|
self, ply_folder, basic_parts, tmp_path, patched_client_class
|
|
954
977
|
):
|
|
978
|
+
# A failed search must NOT silently fall through to creating a project
|
|
979
|
+
# (that path produced duplicate projects). The error should surface.
|
|
955
980
|
from requests import HTTPError
|
|
956
|
-
created = MagicMock()
|
|
957
|
-
created.id = "fallback-pid"
|
|
958
981
|
first_client = MagicMock()
|
|
959
|
-
first_client.projects.list.side_effect = HTTPError("
|
|
960
|
-
first_client.projects.create.return_value = created
|
|
982
|
+
first_client.projects.list.side_effect = HTTPError("500")
|
|
961
983
|
patched_client_class.side_effect = [first_client, MagicMock()]
|
|
962
984
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
985
|
+
with pytest.raises(HTTPError):
|
|
986
|
+
self._build_sim(
|
|
987
|
+
ply_folder, basic_parts, tmp_path,
|
|
988
|
+
project_name="xyz",
|
|
989
|
+
create_project_if_needed=True,
|
|
990
|
+
)
|
|
968
991
|
|
|
969
|
-
|
|
970
|
-
first_client.projects.create.assert_called_once()
|
|
992
|
+
first_client.projects.create.assert_not_called()
|
|
971
993
|
|
|
972
994
|
|
|
973
995
|
class TestFindOrCreateProjectCancel:
|
|
@@ -991,6 +1013,7 @@ class TestFindOrCreateProjectCancel:
|
|
|
991
1013
|
client = patched_client_class.return_value
|
|
992
1014
|
existing = MagicMock()
|
|
993
1015
|
existing.id = "pid-1"
|
|
1016
|
+
existing.name = "existing"
|
|
994
1017
|
client.projects.list.return_value = [existing]
|
|
995
1018
|
# list(q="state:pending") -> [w1]; list(q="state:started") -> [w2]
|
|
996
1019
|
client.workflows.list.side_effect = [
|
|
@@ -1011,6 +1034,7 @@ class TestFindOrCreateProjectCancel:
|
|
|
1011
1034
|
client = patched_client_class.return_value
|
|
1012
1035
|
existing = MagicMock()
|
|
1013
1036
|
existing.id = "pid-1"
|
|
1037
|
+
existing.name = "existing"
|
|
1014
1038
|
client.projects.list.return_value = [existing]
|
|
1015
1039
|
|
|
1016
1040
|
CompressionSimulation.find_or_create_project(
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from metafold.materials import (
|
|
3
|
+
CompMooneyRivlinParams,
|
|
4
|
+
CompNeoHookParams,
|
|
5
|
+
ConstitutiveModel,
|
|
6
|
+
ElasticPlasticParams,
|
|
7
|
+
FlowModel,
|
|
8
|
+
HypoElasticParams,
|
|
9
|
+
JohnsonCookParams,
|
|
10
|
+
Material,
|
|
11
|
+
MaxwellWeichertParams,
|
|
12
|
+
RigidParams,
|
|
13
|
+
StabilityCheck,
|
|
14
|
+
UCNHParams,
|
|
15
|
+
ViscoelasticMode,
|
|
16
|
+
ViscoTransIsoHyperParams,
|
|
17
|
+
YieldCondition,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def roundtrip(material: Material) -> Material:
|
|
22
|
+
return Material.from_dict(material.to_dict())
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestRigidParams:
|
|
26
|
+
def test_roundtrip(self):
|
|
27
|
+
m = Material(
|
|
28
|
+
density=1730.0,
|
|
29
|
+
thermal_conductivity=45.0,
|
|
30
|
+
specific_heat=4.8e-4,
|
|
31
|
+
constitutive_model=ConstitutiveModel(
|
|
32
|
+
params=RigidParams(shear_modulus=2667e6, bulk_modulus=8000e6)
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
36
|
+
|
|
37
|
+
def test_type_key(self):
|
|
38
|
+
d = ConstitutiveModel(params=RigidParams(1.0, 2.0)).to_dict()
|
|
39
|
+
assert d["type"] == "rigid"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestCompMooneyRivlinParams:
|
|
43
|
+
def test_roundtrip(self):
|
|
44
|
+
m = Material(
|
|
45
|
+
density=150.0,
|
|
46
|
+
thermal_conductivity=45.0,
|
|
47
|
+
specific_heat=4.8e-4,
|
|
48
|
+
constitutive_model=ConstitutiveModel(
|
|
49
|
+
params=CompMooneyRivlinParams(he_constant_1=1.5e5, he_constant_2=1.75e5, he_PR=0.41)
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
53
|
+
|
|
54
|
+
def test_type_key(self):
|
|
55
|
+
d = ConstitutiveModel(params=CompMooneyRivlinParams(1.0, 2.0, 0.3)).to_dict()
|
|
56
|
+
assert d["type"] == "comp_mooney_rivlin"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestCompNeoHookParams:
|
|
60
|
+
def test_roundtrip(self):
|
|
61
|
+
m = Material(
|
|
62
|
+
density=100.0,
|
|
63
|
+
thermal_conductivity=1.0,
|
|
64
|
+
specific_heat=1.0,
|
|
65
|
+
constitutive_model=ConstitutiveModel(
|
|
66
|
+
params=CompNeoHookParams(bulk_modulus=1e6, shear_modulus=5e5)
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
70
|
+
|
|
71
|
+
def test_type_key(self):
|
|
72
|
+
d = ConstitutiveModel(params=CompNeoHookParams(1.0, 2.0)).to_dict()
|
|
73
|
+
assert d["type"] == "comp_neo_hook"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TestUCNHParams:
|
|
77
|
+
def test_roundtrip_minimal(self):
|
|
78
|
+
m = Material(
|
|
79
|
+
density=500.0,
|
|
80
|
+
thermal_conductivity=1.0,
|
|
81
|
+
specific_heat=1.0,
|
|
82
|
+
constitutive_model=ConstitutiveModel(
|
|
83
|
+
params=UCNHParams(shear_modulus=1e6, bulk_modulus=2e6, useModifiedEOS=True)
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
87
|
+
|
|
88
|
+
def test_roundtrip_with_plasticity(self):
|
|
89
|
+
m = Material(
|
|
90
|
+
density=500.0,
|
|
91
|
+
thermal_conductivity=1.0,
|
|
92
|
+
specific_heat=1.0,
|
|
93
|
+
constitutive_model=ConstitutiveModel(
|
|
94
|
+
params=UCNHParams(
|
|
95
|
+
shear_modulus=1e6,
|
|
96
|
+
bulk_modulus=2e6,
|
|
97
|
+
useModifiedEOS=False,
|
|
98
|
+
usePlasticity=True,
|
|
99
|
+
yield_stress=1e4,
|
|
100
|
+
hardening_modulus=1e3,
|
|
101
|
+
alpha=0.5,
|
|
102
|
+
)
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
106
|
+
|
|
107
|
+
def test_bool_serialised_as_lowercase_string(self):
|
|
108
|
+
p = UCNHParams(shear_modulus=1.0, bulk_modulus=1.0, useModifiedEOS=True)
|
|
109
|
+
assert p.to_dict()["useModifiedEOS"] == "true"
|
|
110
|
+
p2 = UCNHParams(shear_modulus=1.0, bulk_modulus=1.0, useModifiedEOS=False)
|
|
111
|
+
assert p2.to_dict()["useModifiedEOS"] == "false"
|
|
112
|
+
|
|
113
|
+
def test_optional_fields_omitted_when_none(self):
|
|
114
|
+
p = UCNHParams(shear_modulus=1.0, bulk_modulus=1.0, useModifiedEOS=True)
|
|
115
|
+
d = p.to_dict()
|
|
116
|
+
assert "usePlasticity" not in d
|
|
117
|
+
assert "yield_stress" not in d
|
|
118
|
+
|
|
119
|
+
def test_type_key(self):
|
|
120
|
+
d = ConstitutiveModel(params=UCNHParams(1.0, 2.0, True)).to_dict()
|
|
121
|
+
assert d["type"] == "UCNH"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class TestMaxwellWeichertParams:
|
|
125
|
+
def test_roundtrip(self):
|
|
126
|
+
m = Material(
|
|
127
|
+
density=200.0,
|
|
128
|
+
thermal_conductivity=45.0,
|
|
129
|
+
specific_heat=4.8e-4,
|
|
130
|
+
constitutive_model=ConstitutiveModel(
|
|
131
|
+
params=MaxwellWeichertParams(
|
|
132
|
+
bulk_modulus=931250,
|
|
133
|
+
terminal_shear_modulus=43750,
|
|
134
|
+
viscoelastic_series=[
|
|
135
|
+
ViscoelasticMode("mode1", 0.15, 25000),
|
|
136
|
+
ViscoelasticMode("mode2", 0.20, 30000),
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
142
|
+
|
|
143
|
+
def test_viscoelastic_modes_preserved(self):
|
|
144
|
+
params = MaxwellWeichertParams(
|
|
145
|
+
bulk_modulus=1.0,
|
|
146
|
+
terminal_shear_modulus=1.0,
|
|
147
|
+
viscoelastic_series=[
|
|
148
|
+
ViscoelasticMode("mode1", 0.1, 100.0),
|
|
149
|
+
ViscoelasticMode("mode2", 0.2, 200.0),
|
|
150
|
+
],
|
|
151
|
+
)
|
|
152
|
+
d = params.to_dict()
|
|
153
|
+
assert len(d["viscoelastic_series"]) == 2
|
|
154
|
+
assert d["viscoelastic_series"][0]["mode"] == "mode1"
|
|
155
|
+
assert d["viscoelastic_series"][1]["relaxation_time"] == 0.2
|
|
156
|
+
|
|
157
|
+
def test_type_key(self):
|
|
158
|
+
d = ConstitutiveModel(
|
|
159
|
+
params=MaxwellWeichertParams(1.0, 1.0, [])
|
|
160
|
+
).to_dict()
|
|
161
|
+
assert d["type"] == "Maxwell_Weichert"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class TestElasticPlasticParams:
|
|
165
|
+
def test_roundtrip(self):
|
|
166
|
+
m = Material(
|
|
167
|
+
density=7850.0,
|
|
168
|
+
thermal_conductivity=45.0,
|
|
169
|
+
specific_heat=4.8e-4,
|
|
170
|
+
constitutive_model=ConstitutiveModel(
|
|
171
|
+
params=ElasticPlasticParams(
|
|
172
|
+
shear_modulus=8e10,
|
|
173
|
+
bulk_modulus=1.6e11,
|
|
174
|
+
yield_condition=YieldCondition(type="vonMises"),
|
|
175
|
+
stability_check=StabilityCheck(type="drucker"),
|
|
176
|
+
flow_model=FlowModel(
|
|
177
|
+
params=JohnsonCookParams(A=0.9e9, B=0.5e9, C=0.014, n=0.26, m=1.03)
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
183
|
+
|
|
184
|
+
def test_type_key(self):
|
|
185
|
+
d = ConstitutiveModel(
|
|
186
|
+
params=ElasticPlasticParams(
|
|
187
|
+
shear_modulus=1.0,
|
|
188
|
+
bulk_modulus=1.0,
|
|
189
|
+
yield_condition=YieldCondition("vonMises"),
|
|
190
|
+
stability_check=StabilityCheck("drucker"),
|
|
191
|
+
flow_model=FlowModel(params=JohnsonCookParams(1, 1, 1, 1, 1)),
|
|
192
|
+
)
|
|
193
|
+
).to_dict()
|
|
194
|
+
assert d["type"] == "elastic_plastic"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class TestHypoElasticParams:
|
|
198
|
+
def test_roundtrip(self):
|
|
199
|
+
m = Material(
|
|
200
|
+
density=1000.0,
|
|
201
|
+
thermal_conductivity=1.0,
|
|
202
|
+
specific_heat=1.0,
|
|
203
|
+
constitutive_model=ConstitutiveModel(
|
|
204
|
+
params=HypoElasticParams(G=1.5e6, K=3.0e6)
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
208
|
+
|
|
209
|
+
def test_type_key(self):
|
|
210
|
+
d = ConstitutiveModel(params=HypoElasticParams(G=1.0, K=2.0)).to_dict()
|
|
211
|
+
assert d["type"] == "hypo_elastic"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TestViscoTransIsoHyperParams:
|
|
215
|
+
def _make_params(self, **overrides):
|
|
216
|
+
defaults = dict(
|
|
217
|
+
bulk_modulus=1.0e6,
|
|
218
|
+
c1=1.0,
|
|
219
|
+
c2=2.0,
|
|
220
|
+
c3=3.0,
|
|
221
|
+
c4=4.0,
|
|
222
|
+
c5=5.0,
|
|
223
|
+
fiber_stretch=1.1,
|
|
224
|
+
direction_of_symm=[0.0, 1.0, 0.0],
|
|
225
|
+
failure_option=0,
|
|
226
|
+
max_fiber_strain=0.1,
|
|
227
|
+
max_matrix_strain=0.2,
|
|
228
|
+
y1=0.1, y2=0.2, y3=0.3, y4=0.4, y5=0.5, y6=0.6,
|
|
229
|
+
t1=1.0, t2=2.0, t3=3.0, t4=4.0, t5=5.0, t6=6.0,
|
|
230
|
+
)
|
|
231
|
+
defaults.update(overrides)
|
|
232
|
+
return ViscoTransIsoHyperParams(**defaults)
|
|
233
|
+
|
|
234
|
+
def test_roundtrip(self):
|
|
235
|
+
m = Material(
|
|
236
|
+
density=1200.0,
|
|
237
|
+
thermal_conductivity=0.5,
|
|
238
|
+
specific_heat=1.0e-3,
|
|
239
|
+
constitutive_model=ConstitutiveModel(params=self._make_params()),
|
|
240
|
+
)
|
|
241
|
+
assert roundtrip(m).to_dict() == m.to_dict()
|
|
242
|
+
|
|
243
|
+
def test_type_key(self):
|
|
244
|
+
d = ConstitutiveModel(params=self._make_params()).to_dict()
|
|
245
|
+
assert d["type"] == "visco_trans_iso_hyper"
|
|
246
|
+
|
|
247
|
+
def test_int_inputs_serialized_as_float(self):
|
|
248
|
+
params = self._make_params(c3=0, c4=0, c5=0, direction_of_symm=[0, 1, 0])
|
|
249
|
+
d = params.to_dict()
|
|
250
|
+
assert isinstance(d["c3"], float)
|
|
251
|
+
assert isinstance(d["c4"], float)
|
|
252
|
+
assert isinstance(d["c5"], float)
|
|
253
|
+
assert d["direction_of_symm"] == [0.0, 1.0, 0.0]
|
|
254
|
+
assert all(isinstance(x, float) for x in d["direction_of_symm"])
|
|
255
|
+
|
|
256
|
+
def test_failure_option_serialized_as_int(self):
|
|
257
|
+
params = self._make_params(failure_option=1)
|
|
258
|
+
d = params.to_dict()
|
|
259
|
+
assert isinstance(d["failure_option"], int)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class TestMaterialOptionalFields:
|
|
263
|
+
def test_optional_fields_omitted_when_none(self):
|
|
264
|
+
m = Material(density=1.0, thermal_conductivity=1.0, specific_heat=1.0,
|
|
265
|
+
constitutive_model=ConstitutiveModel(params=RigidParams(1.0, 1.0)))
|
|
266
|
+
d = m.to_dict()
|
|
267
|
+
assert "room_temp" not in d
|
|
268
|
+
assert "melt_temp" not in d
|
|
269
|
+
|
|
270
|
+
def test_optional_fields_roundtrip(self):
|
|
271
|
+
m = Material(
|
|
272
|
+
density=1.0,
|
|
273
|
+
thermal_conductivity=1.0,
|
|
274
|
+
specific_heat=1.0,
|
|
275
|
+
constitutive_model=ConstitutiveModel(params=RigidParams(1.0, 1.0)),
|
|
276
|
+
room_temp=300.0,
|
|
277
|
+
melt_temp=1500.0,
|
|
278
|
+
)
|
|
279
|
+
r = roundtrip(m)
|
|
280
|
+
assert r.room_temp == 300.0
|
|
281
|
+
assert r.melt_temp == 1500.0
|
|
@@ -114,6 +114,39 @@ class TestBuildParts:
|
|
|
114
114
|
parts = _build_parts([{"type": "piston_mesh", "file": "piston.ply", "velocity": velocity}])
|
|
115
115
|
assert parts[0].velocity == velocity
|
|
116
116
|
|
|
117
|
+
def test_piston_mesh_default_material_when_omitted(self):
|
|
118
|
+
from metafold.materials import DEFAULT_PISTON_MATERIAL
|
|
119
|
+
parts = _build_parts([{"type": "piston_mesh", "file": "piston.ply"}])
|
|
120
|
+
assert parts[0].material is DEFAULT_PISTON_MATERIAL
|
|
121
|
+
|
|
122
|
+
def test_piston_mesh_with_preset_material(self):
|
|
123
|
+
parts = _build_parts([
|
|
124
|
+
{"type": "piston_mesh", "file": "piston.ply", "material": "material_aluminum"}
|
|
125
|
+
])
|
|
126
|
+
assert parts[0].material is MATERIAL_ALUMINUM
|
|
127
|
+
|
|
128
|
+
def test_piston_mesh_with_inline_material(self):
|
|
129
|
+
parts = _build_parts([
|
|
130
|
+
{
|
|
131
|
+
"type": "piston_mesh",
|
|
132
|
+
"file": "piston.ply",
|
|
133
|
+
"material": {
|
|
134
|
+
"density": 1730.0,
|
|
135
|
+
"thermal_conductivity": 45,
|
|
136
|
+
"specific_heat": 4.8e-4,
|
|
137
|
+
"constitutive_model": {
|
|
138
|
+
"type": "rigid",
|
|
139
|
+
"params": {"shear_modulus": 2667.0e6, "bulk_modulus": 8000.0e6},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
])
|
|
144
|
+
assert parts[0].material.constitutive_model.params.get_type() == "rigid"
|
|
145
|
+
|
|
146
|
+
def test_piston_cylinder_with_material(self):
|
|
147
|
+
parts = _build_parts([{"type": "piston_cylinder", "material": "material_aluminum"}])
|
|
148
|
+
assert parts[0].material is MATERIAL_ALUMINUM
|
|
149
|
+
|
|
117
150
|
def test_mesh_part_with_preset_material(self):
|
|
118
151
|
parts = _build_parts([
|
|
119
152
|
{
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|