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.
- {metafold-0.12.dev11 → metafold-0.13.dev0}/PKG-INFO +3 -3
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/simulation/compression_simulation.py +90 -44
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/PKG-INFO +3 -3
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/requires.txt +1 -1
- {metafold-0.12.dev11 → metafold-0.13.dev0}/pyproject.toml +5 -5
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_compression_simulation.py +24 -4
- {metafold-0.12.dev11 → metafold-0.13.dev0}/LICENSE +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/README.md +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/__init__.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/api.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/assets.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/auth.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/client.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/exceptions.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/jobs.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/materials.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/projects.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/simulation/__init__.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/simulation/compression_experiment.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/simulation/run_experiment.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/utils.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold/workflows.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/SOURCES.txt +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/dependency_links.txt +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/metafold.egg-info/top_level.txt +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/setup.cfg +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_assets.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_compession_experiment.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_jobs.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_materials.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_projects.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_run_experiment.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_shear_simulation.py +0 -0
- {metafold-0.12.dev11 → metafold-0.13.dev0}/tests/test_utils.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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":
|
|
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":
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "metafold"
|
|
7
|
-
version = "0.
|
|
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.
|
|
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.
|
|
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 = "
|
|
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 = "
|
|
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
|
|
171
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|