metafold 0.13.2.dev0__tar.gz → 0.14.dev0__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.
Files changed (35) hide show
  1. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/PKG-INFO +1 -1
  2. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/simulation/compression_simulation.py +17 -0
  3. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/simulation/run_experiment.py +7 -0
  4. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/PKG-INFO +1 -1
  5. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/pyproject.toml +1 -1
  6. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_compression_simulation.py +24 -71
  7. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/LICENSE +0 -0
  8. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/README.md +0 -0
  9. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/__init__.py +0 -0
  10. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/api.py +0 -0
  11. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/assets.py +0 -0
  12. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/auth.py +0 -0
  13. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/client.py +0 -0
  14. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/exceptions.py +0 -0
  15. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/jobs.py +0 -0
  16. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/materials.py +0 -0
  17. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/projects.py +0 -0
  18. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/simulation/__init__.py +0 -0
  19. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/simulation/compression_experiment.py +0 -0
  20. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/utils.py +0 -0
  21. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold/workflows.py +0 -0
  22. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/SOURCES.txt +0 -0
  23. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/dependency_links.txt +0 -0
  24. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/requires.txt +0 -0
  25. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/metafold.egg-info/top_level.txt +0 -0
  26. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/setup.cfg +0 -0
  27. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_assets.py +0 -0
  28. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_compession_experiment.py +0 -0
  29. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_jobs.py +0 -0
  30. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_materials.py +0 -0
  31. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_projects.py +0 -0
  32. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_run_experiment.py +0 -0
  33. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_shear_simulation.py +0 -0
  34. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_utils.py +0 -0
  35. {metafold-0.13.2.dev0 → metafold-0.14.dev0}/tests/test_workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metafold
3
- Version: 0.13.2.dev0
3
+ Version: 0.14.dev0
4
4
  Summary: Metafold SDK for Python
5
5
  Author-email: Metafold 3D <info@metafold3d.com>
6
6
  License: Copyright 2024 Metafold 3D
@@ -245,12 +245,21 @@ class ExperimentParallelepiped(ExperimentPrimitive):
245
245
  return pts.min(axis=0), pts.max(axis=0)
246
246
 
247
247
 
248
+ @dataclass
249
+ class ForceLimit:
250
+ face: str
251
+ value: float
252
+
253
+
248
254
  @dataclass
249
255
  class ExperimentPistonBase:
250
256
  velocity: Optional[list] = field(default_factory=lambda: DEFAULT_PISTON_VELOCITY)
251
257
  contact_type: str = "rigid"
252
258
  mu: float = 0.0
253
259
 
260
+ force_limit: ForceLimit | None = None
261
+ """Apply condition to jump to zero velocity when the given force is reached."""
262
+
254
263
 
255
264
  @dataclass
256
265
  class ExperimentPistonMesh(ExperimentMesh, ExperimentPistonBase):
@@ -1287,6 +1296,13 @@ class CompressionSimulation:
1287
1296
  if info.disabled:
1288
1297
  continue
1289
1298
  if isinstance(info.part, (ExperimentPistonBase, ExperimentSupportBase)):
1299
+ kwargs = {}
1300
+ if isinstance(info.part, ExperimentPistonBase) and info.part.force_limit:
1301
+ kwargs.update({
1302
+ "condition": info.part.force_limit.face,
1303
+ "conditionValue": info.part.force_limit.value,
1304
+ "conditionAction": "GoToZeroVelocity",
1305
+ })
1290
1306
  rigid_contacts.append(
1291
1307
  Contact(
1292
1308
  type=info.part.contact_type,
@@ -1295,6 +1311,7 @@ class CompressionSimulation:
1295
1311
  master_material=info.material_index,
1296
1312
  direction=[1, 1, 1],
1297
1313
  materials=deformable_indices,
1314
+ **kwargs
1298
1315
  )
1299
1316
  )
1300
1317
 
@@ -158,6 +158,7 @@ from metafold.simulation.compression_simulation import (
158
158
  ExperimentPistonBox,
159
159
  ExperimentPistonCylinder,
160
160
  ExperimentPistonMesh,
161
+ ForceLimit,
161
162
  SimulationParameters,
162
163
  WorkflowStep,
163
164
  validate_simulation_parts,
@@ -202,6 +203,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
202
203
  kwargs["shape_parameters"] = entry["shape_parameters"]
203
204
  if "material" in entry:
204
205
  kwargs["material"] = _resolve_material(entry["material"])
206
+ if "force_limit" in entry:
207
+ kwargs["force_limit"] = ForceLimit(**entry["force_limit"])
205
208
  parts.append(ExperimentPistonCylinder(**kwargs))
206
209
 
207
210
  elif part_type == "piston_box":
@@ -214,6 +217,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
214
217
  kwargs["shape_parameters"] = entry["shape_parameters"]
215
218
  if "material" in entry:
216
219
  kwargs["material"] = _resolve_material(entry["material"])
220
+ if "force_limit" in entry:
221
+ kwargs["force_limit"] = ForceLimit(**entry["force_limit"])
217
222
  parts.append(ExperimentPistonBox(**kwargs))
218
223
 
219
224
  elif part_type == "piston_mesh":
@@ -224,6 +229,8 @@ def _build_parts(parts_config: list[dict]) -> list[ExperimentPart]:
224
229
  kwargs["velocity"] = entry["velocity"]
225
230
  if "material" in entry:
226
231
  kwargs["material"] = _resolve_material(entry["material"])
232
+ if "force_limit" in entry:
233
+ kwargs["force_limit"] = ForceLimit(**entry["force_limit"])
227
234
  parts.append(ExperimentPistonMesh(**kwargs))
228
235
 
229
236
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metafold
3
- Version: 0.13.2.dev0
3
+ Version: 0.14.dev0
4
4
  Summary: Metafold SDK for Python
5
5
  Author-email: Metafold 3D <info@metafold3d.com>
6
6
  License: Copyright 2024 Metafold 3D
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "metafold"
7
- version = "0.13.2.dev0"
7
+ version = "0.14.dev0"
8
8
  authors = [
9
9
  {name = "Metafold 3D", email = "info@metafold3d.com"},
10
10
  ]
@@ -1,4 +1,5 @@
1
1
  from io import BytesIO
2
+ from copy import deepcopy
2
3
  from types import SimpleNamespace
3
4
  from zipfile import ZipFile
4
5
 
@@ -16,6 +17,7 @@ from metafold.simulation.compression_simulation import (
16
17
  ExperimentPistonCylinder,
17
18
  ExperimentPistonMesh,
18
19
  ExperimentSupportMesh,
20
+ ForceLimit,
19
21
  SimulationParameters,
20
22
  WorkflowStep,
21
23
  WorkflowStepType,
@@ -725,6 +727,28 @@ jobs:
725
727
  assert workflow_params[key] == value, f"mismatch for {key}: {workflow_params[key]!r} != {value!r}"
726
728
 
727
729
 
730
+ class TestSimConfig:
731
+ def test_force_limit(self, ply_folder, basic_parts, tmp_path):
732
+ parts = deepcopy(basic_parts)
733
+ parts[0].force_limit = ForceLimit("zminus", 2000)
734
+ sim = CompressionSimulation(
735
+ parts=parts,
736
+ simulation_name="test_sim",
737
+ stl_folder_path=str(ply_folder),
738
+ output_path=str(tmp_path / "out"),
739
+ client=MagicMock(),
740
+ )
741
+ sim.create_sim_config()
742
+ contact = sim.ups.findall("./MaterialProperties/MPM/contact")[0]
743
+ for tag, value in [
744
+ ("condition", "zminus"),
745
+ ("conditionValue", "2000"),
746
+ ("conditionAction", "GoToZeroVelocity"),
747
+ ]:
748
+ assert contact.find(tag) is not None
749
+ assert contact.find(tag).text == value
750
+
751
+
728
752
  class TestStressStrainPerPart:
729
753
  """stress-strain runs once per deformable part (not one representative).
730
754
  It is opt-in: callers must add the step to workflow_steps."""
@@ -1439,74 +1463,3 @@ class TestFindOrCreateProjectCancel:
1439
1463
 
1440
1464
  assert pid == "new-pid"
1441
1465
  client.workflows.cancel.assert_not_called()
1442
-
1443
-
1444
- class TestFindOrCreateProjectUserScope:
1445
- """With user set, only the caller's own project is reused; a same-named
1446
- project owned by someone else (visible under org access) is ignored."""
1447
-
1448
- @pytest.fixture
1449
- def patched_client_class(self, monkeypatch):
1450
- from metafold.simulation import compression_simulation
1451
- fake_class = MagicMock()
1452
- monkeypatch.setattr(compression_simulation, "MetafoldClient", fake_class)
1453
- return fake_class
1454
-
1455
- def _project(self, pid, name, user):
1456
- p = MagicMock()
1457
- p.id = pid
1458
- p.name = name
1459
- p.user = user
1460
- return p
1461
-
1462
- def test_ignores_same_name_owned_by_other_user(self, patched_client_class):
1463
- client = patched_client_class.return_value
1464
- # A defensive belt-and-suspenders case: even if the API returned another
1465
- # user's project, the Python filter drops it and we create a new one.
1466
- client.projects.list.return_value = [self._project("other-pid", "shared", "userB")]
1467
- client.projects.create.return_value = self._project("new-pid", "shared", "userA")
1468
-
1469
- pid = CompressionSimulation.find_or_create_project(
1470
- "shared", access_token="tok", base_url="http://x/", user="userA",
1471
- )
1472
-
1473
- assert pid == "new-pid"
1474
- client.projects.create.assert_called_once()
1475
-
1476
- def test_reuses_callers_own_project(self, patched_client_class):
1477
- client = patched_client_class.return_value
1478
- client.projects.list.return_value = [self._project("mine", "shared", "userA")]
1479
-
1480
- pid = CompressionSimulation.find_or_create_project(
1481
- "shared", access_token="tok", base_url="http://x/", user="userA",
1482
- cancel_existing_workflows=False,
1483
- )
1484
-
1485
- assert pid == "mine"
1486
- client.projects.create.assert_not_called()
1487
-
1488
- def test_scopes_query_to_user(self, patched_client_class):
1489
- client = patched_client_class.return_value
1490
- client.projects.list.return_value = []
1491
- client.projects.create.return_value = self._project("new-pid", "shared", "userA")
1492
-
1493
- CompressionSimulation.find_or_create_project(
1494
- "shared", access_token="tok", base_url="http://x/", user="userA",
1495
- )
1496
-
1497
- q = client.projects.list.call_args.kwargs["q"]
1498
- assert 'name:"shared"' in q
1499
- assert 'user:"userA"' in q
1500
-
1501
- def test_no_user_keeps_unscoped_behavior(self, patched_client_class):
1502
- client = patched_client_class.return_value
1503
- client.projects.list.return_value = [self._project("any-pid", "shared", "userB")]
1504
-
1505
- pid = CompressionSimulation.find_or_create_project(
1506
- "shared", access_token="tok", base_url="http://x/",
1507
- cancel_existing_workflows=False,
1508
- )
1509
-
1510
- assert pid == "any-pid"
1511
- q = client.projects.list.call_args.kwargs["q"]
1512
- assert "user:" not in q
File without changes
File without changes
File without changes