metafold 0.12.dev11__tar.gz → 0.13.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.12.dev11 → metafold-0.13.dev0}/PKG-INFO +3 -3
  2. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/simulation/compression_simulation.py +90 -44
  3. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/PKG-INFO +3 -3
  4. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/requires.txt +1 -1
  5. {metafold-0.12.dev11 → metafold-0.13.dev0}/pyproject.toml +5 -5
  6. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_compression_simulation.py +24 -4
  7. {metafold-0.12.dev11 → metafold-0.13.dev0}/LICENSE +0 -0
  8. {metafold-0.12.dev11 → metafold-0.13.dev0}/README.md +0 -0
  9. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/__init__.py +0 -0
  10. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/api.py +0 -0
  11. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/assets.py +0 -0
  12. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/auth.py +0 -0
  13. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/client.py +0 -0
  14. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/exceptions.py +0 -0
  15. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/jobs.py +0 -0
  16. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/materials.py +0 -0
  17. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/projects.py +0 -0
  18. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/simulation/__init__.py +0 -0
  19. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/simulation/compression_experiment.py +0 -0
  20. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/simulation/run_experiment.py +0 -0
  21. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/utils.py +0 -0
  22. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/workflows.py +0 -0
  23. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/SOURCES.txt +0 -0
  24. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/dependency_links.txt +0 -0
  25. {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/top_level.txt +0 -0
  26. {metafold-0.12.dev11 → metafold-0.13.dev0}/setup.cfg +0 -0
  27. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_assets.py +0 -0
  28. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_compession_experiment.py +0 -0
  29. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_jobs.py +0 -0
  30. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_materials.py +0 -0
  31. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_projects.py +0 -0
  32. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_run_experiment.py +0 -0
  33. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_shear_simulation.py +0 -0
  34. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_utils.py +0 -0
  35. {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metafold
3
- Version: 0.12.dev11
3
+ Version: 0.13.dev0
4
4
  Summary: Metafold SDK for Python
5
5
  Author-email: Metafold 3D <info@metafold3d.com>
6
6
  License: Copyright 2024 Metafold 3D
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
26
  Classifier: Topic :: Scientific/Engineering
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
- Requires-Python: >=3.12
28
+ Requires-Python: >=3.11
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: attrs~=23.2
@@ -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.2; extra == "simulation"
41
+ Requires-Dist: simulation-configurator==0.2.*; extra == "simulation"
42
42
  Requires-Dist: tables>=3.11.1; extra == "simulation"
43
43
  Dynamic: license-file
44
44
 
@@ -8,7 +8,7 @@ import uuid
8
8
 
9
9
  from requests import HTTPError
10
10
  import yaml
11
- from dotenv import load_dotenv
11
+ from dotenv import load_dotenv # type: ignore
12
12
  from metafold import MetafoldClient
13
13
  from pathlib import Path
14
14
  from numpy.typing import DTypeLike
@@ -311,6 +311,7 @@ class WorkflowStepType(Enum):
311
311
  STRESS_STRAIN = "stress_strain"
312
312
  PARTICLE_DISPLACEMENT = "particle_displacement"
313
313
  ENERGY_METRICS = "energy_metrics"
314
+ REDUCE_RESULTS = "reduce_results"
314
315
 
315
316
 
316
317
  class WorkflowStep:
@@ -353,6 +354,8 @@ class CompressionSimulation:
353
354
  bvh_filename: Optional[str] = None
354
355
  # Sampling resolution (longest axis) assigned for the pass-2 sample job.
355
356
  sample_resolution: Optional[int] = None
357
+ # From the prep metrics job; None until prep completes.
358
+ interior_volume: Optional[float] = None
356
359
  patch: dict = field(default_factory=dict)
357
360
  jobs: dict[str, Any] = field(default_factory=lambda: {})
358
361
  part_unique_name = ""
@@ -374,6 +377,7 @@ class CompressionSimulation:
374
377
  "is_piston": isinstance(self.part, ExperimentPistonBase),
375
378
  "part_name": self.part.name,
376
379
  "disabled": self.disabled,
380
+ "interior_volume": self.interior_volume,
377
381
  }
378
382
 
379
383
  def restore_from_state_dict(self, saved: dict):
@@ -382,6 +386,7 @@ class CompressionSimulation:
382
386
  self.material_name = saved["material_name"]
383
387
  self.jobs = saved["jobs"]
384
388
  self.disabled = saved.get("disabled", False)
389
+ self.interior_volume = saved.get("interior_volume")
385
390
 
386
391
  client: MetafoldClient
387
392
  project_id: str = ""
@@ -425,7 +430,7 @@ class CompressionSimulation:
425
430
  simulation_name: str,
426
431
  project_id: str = "",
427
432
  stl_folder_path: str = "PLY",
428
- env_source: str = ".env.dev",
433
+ env_source: str | None = ".env.dev",
429
434
  output_path: str = "",
430
435
  client: Optional[MetafoldClient] = None,
431
436
  simulation_parameters: SimulationParameters = SimulationParameters(),
@@ -440,6 +445,7 @@ class CompressionSimulation:
440
445
  # stress_strain is opt-in (not default): callers add it via workflow_steps.
441
446
  WorkflowStep(WorkflowStepType.PARTICLE_DISPLACEMENT),
442
447
  WorkflowStep(WorkflowStepType.ENERGY_METRICS),
448
+ # reduce-results is auto-added in build_workflow (not a step).
443
449
  ],
444
450
  force_reupload_files=False,
445
451
  prep_workflow_batch_size: int = 10,
@@ -642,7 +648,8 @@ class CompressionSimulation:
642
648
  return created.id
643
649
 
644
650
  def setup_client(self, env_source, project_id) -> MetafoldClient:
645
- load_dotenv(env_source)
651
+ if env_source is not None:
652
+ load_dotenv(env_source)
646
653
 
647
654
  if not project_id:
648
655
  self.project_id = os.environ.get("METAFOLD_PROJECT_ID", "")
@@ -824,6 +831,24 @@ class CompressionSimulation:
824
831
  params[f"{sample_mesh_job}.resolution"] = f"{resolution}"
825
832
  part_info.jobs["sample-mesh"] = sample_mesh_job
826
833
 
834
+ # Metrics run in prep (not the main workflow) so interior volume
835
+ # is known before dispatch, when the server manifest is uploaded.
836
+ if self._is_analysis_target(part_info.part) and self._get_step(
837
+ WorkflowStepType.METRICS, part_info.part.name
838
+ ):
839
+ metrics_job = f"metrics-{unique_name}"
840
+ jobs[metrics_job] = {
841
+ "type": "implicit/metrics",
842
+ "needs": [sample_mesh_job],
843
+ "assets": {"volume": sample_mesh_job},
844
+ "parameters": {
845
+ "volume_size": {"job": sample_mesh_job, "parameter": "patch_size"},
846
+ "volume_offset": {"job": sample_mesh_job, "parameter": "patch_offset"},
847
+ "volume_resolution": {"job": sample_mesh_job, "parameter": "patch_resolution"},
848
+ },
849
+ }
850
+ part_info.jobs["metrics"] = metrics_job
851
+
827
852
  workflow_yaml = yaml.dump({"jobs": jobs}, default_flow_style=False)
828
853
  return workflow_yaml, params, assets
829
854
 
@@ -1025,6 +1050,30 @@ class CompressionSimulation:
1025
1050
  if volume_asset is not None:
1026
1051
  info.volume_filename = volume_asset.filename
1027
1052
 
1053
+ metrics_job = prep_workflow_jobs.get(info.jobs.get("metrics", ""))
1054
+ if metrics_job is not None:
1055
+ raw = metrics_job.outputs.params.get("interior_volume")
1056
+ if raw is not None:
1057
+ info.interior_volume = float(raw)
1058
+
1059
+ def _total_interior_volume(self, workflow: Optional[Workflow] = None) -> float:
1060
+ """Sum interior volumes across analysis-target parts. Falls back to
1061
+ the main-workflow metrics readback for experiments dispatched before
1062
+ metrics moved to the prep workflow."""
1063
+ total = 0.0
1064
+ for part_info in self.part_infos:
1065
+ if not self._is_analysis_target(part_info.part) or part_info.disabled:
1066
+ continue
1067
+ if part_info.interior_volume is not None:
1068
+ total += part_info.interior_volume
1069
+ continue
1070
+ metrics_job = part_info.jobs.get("metrics", "")
1071
+ if workflow is not None and metrics_job:
1072
+ raw = workflow.get_parameter(f"{metrics_job}.interior_volume")
1073
+ if raw is not None:
1074
+ total += float(raw)
1075
+ return total
1076
+
1028
1077
  def get_part_info(self, name):
1029
1078
  for info in self.part_infos:
1030
1079
  if info.part.name == name:
@@ -1218,6 +1267,9 @@ class CompressionSimulation:
1218
1267
  ElementTree.indent(self.ups)
1219
1268
 
1220
1269
  def _add_to_workflow_metrics(self, part_infos: list[PartInfo]):
1270
+ # Duplicates the prep metrics jobs (which feed the server manifest):
1271
+ # removing these changed main-workflow scheduling and left it pending,
1272
+ # so they stay until that's understood.
1221
1273
  for part_info in part_infos:
1222
1274
  part = part_info.part
1223
1275
  if not self._is_analysis_target(part):
@@ -1241,7 +1293,6 @@ class CompressionSimulation:
1241
1293
  self.workflow_params[f"{metrics_job_name}.volume_resolution"] = json.dumps(
1242
1294
  part_info.patch["resolution"]
1243
1295
  )
1244
- part_info.jobs["metrics"] = metrics_job_name
1245
1296
 
1246
1297
  def _add_to_workflow_compress(self, part_infos: list[PartInfo]):
1247
1298
  compress_job_name = "compress"
@@ -1426,6 +1477,25 @@ class CompressionSimulation:
1426
1477
  part_infos, "force-displacement", "energy-metrics"
1427
1478
  )
1428
1479
 
1480
+ # One merged reduce-results job consumes the particle-data outputs the
1481
+ # browser renders and produces a compact (trimmed + int16 + decimated) HDF5.
1482
+ # Auto-added whenever compress ran; the manifest points DTB at its output.
1483
+ _REDUCE_SOURCE_JOBS = [
1484
+ "compress", "von-mises-stress", "effective-strain",
1485
+ "particle-displacement",
1486
+ ]
1487
+ _REDUCE_JOB_NAME = "reduce-results"
1488
+
1489
+ def _add_to_workflow_reduce_results(self):
1490
+ sources = [j for j in self._REDUCE_SOURCE_JOBS if j in self.workflow_jobs]
1491
+ if "compress" not in sources:
1492
+ return # nothing to preview without particle positions
1493
+ self.workflow_jobs[self._REDUCE_JOB_NAME] = {
1494
+ "type": "sim/postprocess/reduce-results",
1495
+ "needs": sources,
1496
+ "assets": {"data": [{"job": s, "asset": "output"} for s in sources]},
1497
+ }
1498
+
1429
1499
  def build_workflow(self, name_suffix=""):
1430
1500
  self.workflow_yaml = ""
1431
1501
  self.workflow_params = {}
@@ -1462,6 +1532,9 @@ class CompressionSimulation:
1462
1532
  elif step.type == WorkflowStepType.COMPUTE_BVH:
1463
1533
  pass # this is done on the prep workflow, not here
1464
1534
 
1535
+ # Always emit the browser preview when there's particle data to preview.
1536
+ self._add_to_workflow_reduce_results()
1537
+
1465
1538
  self.workflow_yaml = yaml.dump(
1466
1539
  {"jobs": self.workflow_jobs}, default_flow_style=False, sort_keys=False
1467
1540
  )
@@ -2046,16 +2119,7 @@ class CompressionSimulation:
2046
2119
  data["ply"] = f"{name}/ply/part.####.ply"
2047
2120
 
2048
2121
  # Sum interior volumes from all parts
2049
- total_volume = 0.0
2050
- for part_info in self.part_infos:
2051
- if self._is_analysis_target(part_info.part):
2052
- if part_info.disabled:
2053
- continue
2054
- metrics_job = part_info.jobs.get("metrics", "")
2055
- if metrics_job:
2056
- raw = w.get_parameter(f"{metrics_job}.interior_volume")
2057
- if raw is not None:
2058
- total_volume += float(raw)
2122
+ total_volume = self._total_interior_volume(w)
2059
2123
 
2060
2124
  result.update(
2061
2125
  {
@@ -2279,18 +2343,7 @@ class CompressionSimulation:
2279
2343
  }
2280
2344
 
2281
2345
  # Sum interior volumes across analysis-target parts.
2282
- total_volume = 0.0
2283
- for part_info in self.part_infos:
2284
- if not self._is_analysis_target(part_info.part):
2285
- continue
2286
- if part_info.disabled:
2287
- continue
2288
- metrics_job = part_info.jobs.get("metrics", "")
2289
- if not metrics_job:
2290
- continue
2291
- raw = w.get_parameter(f"{metrics_job}.interior_volume")
2292
- if raw is not None:
2293
- total_volume += float(raw)
2346
+ total_volume = self._total_interior_volume(w)
2294
2347
 
2295
2348
  result.update(
2296
2349
  {
@@ -2384,27 +2437,32 @@ class CompressionSimulation:
2384
2437
  "particle-displacement",
2385
2438
  ),
2386
2439
  ]
2440
+ # Point the particle datasets DTB renders at the merged reduce-results
2441
+ # job (compact int16/decimated), falling back to the raw job if preview
2442
+ # didn't run. Histograms are tiny — leave them on the raw job.
2443
+ reduce_job_id = job_id_lookup.get(self._REDUCE_JOB_NAME, "")
2387
2444
  for step, dataset_root, key_prefix, has_histogram, local_job in full_hdf_refs:
2388
2445
  if not self._contains_step(step):
2389
2446
  continue
2390
- job_id = job_id_lookup.get(local_job, "")
2447
+ raw_job_id = job_id_lookup.get(local_job, "")
2448
+ data_job_id = reduce_job_id or raw_job_id
2391
2449
  for i in range(n_materials):
2392
2450
  if self.part_infos[i].disabled:
2393
2451
  continue
2394
2452
  server_data[f"{key_prefix}{i}"] = {
2395
- "jobId": job_id,
2453
+ "jobId": data_job_id,
2396
2454
  "assetName": "output",
2397
2455
  "path": f"/material{i}/{dataset_root}",
2398
2456
  }
2399
2457
  if has_histogram:
2400
2458
  server_data[f"{key_prefix}Histogram{i}"] = {
2401
- "jobId": job_id,
2459
+ "jobId": raw_job_id,
2402
2460
  "assetName": "output",
2403
2461
  "path": f"/material{i}/{dataset_root}_histogram",
2404
2462
  }
2405
2463
 
2406
2464
  if self._contains_step(WorkflowStepType.COMPRESS):
2407
- job_id = job_id_lookup.get("compress", "")
2465
+ job_id = reduce_job_id or job_id_lookup.get("compress", "")
2408
2466
  for i in range(n_materials):
2409
2467
  if self.part_infos[i].disabled:
2410
2468
  continue
@@ -2445,19 +2503,7 @@ class CompressionSimulation:
2445
2503
  Volume is inlined (from prep workflow parameters); energy values are
2446
2504
  refs that the server resolves from the energy-metrics job output."""
2447
2505
  scalars: dict[str, Any] = {}
2448
-
2449
- total_volume = 0.0
2450
- for part_info in self.part_infos:
2451
- if not self._is_analysis_target(part_info.part):
2452
- continue
2453
- if part_info.disabled:
2454
- continue
2455
- metrics_job = part_info.jobs.get("metrics", "")
2456
- if metrics_job:
2457
- raw = workflow.get_parameter(f"{metrics_job}.interior_volume")
2458
- if raw is not None:
2459
- total_volume += float(raw)
2460
- scalars["volume"] = total_volume
2506
+ scalars["volume"] = self._total_interior_volume(workflow)
2461
2507
 
2462
2508
  if self._contains_step(WorkflowStepType.ENERGY_METRICS):
2463
2509
  em_job_id = job_id_lookup.get("energy-metrics", "")
@@ -2717,7 +2763,7 @@ class CompressionSimulation:
2717
2763
  "heading": "Absorbed Energy (J)",
2718
2764
  "filtering": False,
2719
2765
  }
2720
- )
2766
+ )
2721
2767
  self.manifest["resultsListConfig"].append(
2722
2768
  {
2723
2769
  "key": "loadingEnergy",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metafold
3
- Version: 0.12.dev11
3
+ Version: 0.13.dev0
4
4
  Summary: Metafold SDK for Python
5
5
  Author-email: Metafold 3D <info@metafold3d.com>
6
6
  License: Copyright 2024 Metafold 3D
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
26
  Classifier: Topic :: Scientific/Engineering
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
- Requires-Python: >=3.12
28
+ Requires-Python: >=3.11
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: attrs~=23.2
@@ -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.2; extra == "simulation"
41
+ Requires-Dist: simulation-configurator==0.2.*; extra == "simulation"
42
42
  Requires-Dist: tables>=3.11.1; extra == "simulation"
43
43
  Dynamic: license-file
44
44
 
@@ -9,5 +9,5 @@ dotenv>=0.9.9
9
9
  pandas>=2.3.3
10
10
  plyfile>=1.1.3
11
11
  pyyaml>=6.0.3
12
- simulation-configurator==0.1.2
12
+ simulation-configurator==0.2.*
13
13
  tables>=3.11.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "metafold"
7
- version = "0.12.dev11"
7
+ version = "0.13.dev0"
8
8
  authors = [
9
9
  {name = "Metafold 3D", email = "info@metafold3d.com"},
10
10
  ]
@@ -18,7 +18,7 @@ dependencies = [
18
18
  readme = "README.md"
19
19
  license = {file = "LICENSE"}
20
20
  description = "Metafold SDK for Python"
21
- requires-python = ">=3.12"
21
+ requires-python = ">=3.11"
22
22
  keywords = ["metafold", "api", "sdk", "implicit geometry"]
23
23
  classifiers = [
24
24
  "Development Status :: 3 - Alpha",
@@ -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.2",
41
+ "simulation-configurator==0.2.*",
42
42
  "tables>=3.11.1",
43
43
  ]
44
44
 
@@ -68,7 +68,7 @@ packages = ["metafold", "metafold.simulation"]
68
68
  environments = ["sys_platform == 'linux'"]
69
69
 
70
70
  [tool.uv.sources]
71
- simulation-configurator = { index = "sim-config-idx" }
71
+ simulation-configurator = { index = "gitlab" }
72
72
 
73
73
  [[tool.mypy.overrides]]
74
74
  module = [
@@ -79,7 +79,7 @@ module = [
79
79
  ignore_missing_imports = true
80
80
 
81
81
  [[tool.uv.index]]
82
- name = "sim-config-idx"
82
+ name = "gitlab"
83
83
  url = "https://gitlab.com/api/v4/projects/75460563/packages/pypi/simple"
84
84
  explicit = true
85
85
  authenticate = "always"
@@ -167,13 +167,15 @@ class TestBuildWorkflow:
167
167
  assert isinstance(piston.part, ExperimentPistonBase)
168
168
  assert piston.material_index == 0
169
169
 
170
- def test_build_workflow_has_per_part_metrics_jobs(self, prepared_sim):
171
- yaml_str, _ = prepared_sim.build_workflow()
170
+ def test_prep_workflow_has_per_part_metrics_jobs(self, prepared_sim):
171
+ mesh_parts = [p for p in prepared_sim.part_infos if hasattr(p.part, "filename")]
172
+ yaml_str, _, _ = prepared_sim._build_sample_workflow_for_batch(mesh_parts)
172
173
  loaded = yaml.safe_load(yaml_str)
173
- # one metrics job per non-piston mesh part
174
+ # one metrics job per non-piston mesh part, chained to its sample job
174
175
  for info in prepared_sim.part_infos:
175
176
  if not isinstance(info.part, ExperimentPistonBase) and hasattr(info.part, "filename"):
176
- assert f"metrics-{info.part_unique_name}" in loaded["jobs"]
177
+ job = loaded["jobs"][f"metrics-{info.part_unique_name}"]
178
+ assert job["needs"] == [f"sample-mesh-{info.part_unique_name}"]
177
179
 
178
180
  def test_build_workflow_compress_has_velocity(self, prepared_sim):
179
181
  _, params = prepared_sim.build_workflow()
@@ -577,6 +579,23 @@ jobs:
577
579
  - force-displacement
578
580
  assets:
579
581
  data: force-displacement
582
+ reduce-results:
583
+ type: sim/postprocess/reduce-results
584
+ needs:
585
+ - compress
586
+ - von-mises-stress
587
+ - effective-strain
588
+ - particle-displacement
589
+ assets:
590
+ data:
591
+ - job: compress
592
+ asset: output
593
+ - job: von-mises-stress
594
+ asset: output
595
+ - job: effective-strain
596
+ asset: output
597
+ - job: particle-displacement
598
+ asset: output
580
599
  """
581
600
  assert yaml_str.strip() == expected_yaml.strip()
582
601
 
@@ -1130,6 +1149,7 @@ class TestSetupClient:
1130
1149
  simulation_name="t",
1131
1150
  stl_folder_path=str(ply_folder),
1132
1151
  output_path=str(tmp_path / "out"),
1152
+ env_source=None, # Ensure local .env files do not pollute test environment
1133
1153
  **kwargs,
1134
1154
  )
1135
1155
 
File without changes
File without changes
File without changes