metafold 0.12.dev2__py3-none-any.whl → 0.12.dev4__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.
- metafold/client.py +9 -4
- metafold/simulation/__init__.py +2 -0
- metafold/simulation/compression_simulation.py +153 -21
- metafold/simulation/run_experiment.py +21 -1
- {metafold-0.12.dev2.dist-info → metafold-0.12.dev4.dist-info}/METADATA +1 -1
- {metafold-0.12.dev2.dist-info → metafold-0.12.dev4.dist-info}/RECORD +9 -9
- {metafold-0.12.dev2.dist-info → metafold-0.12.dev4.dist-info}/WHEEL +0 -0
- {metafold-0.12.dev2.dist-info → metafold-0.12.dev4.dist-info}/licenses/LICENSE +0 -0
- {metafold-0.12.dev2.dist-info → metafold-0.12.dev4.dist-info}/top_level.txt +0 -0
metafold/client.py
CHANGED
|
@@ -55,11 +55,16 @@ class Client:
|
|
|
55
55
|
headers = {"Authorization": f"Bearer {self._auth.get_token()}"}
|
|
56
56
|
r: Response = request(url, *args, **kwargs, headers=headers)
|
|
57
57
|
if not r.ok:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
# Not all error responses are JSON so fall back to the status reason
|
|
59
|
+
try:
|
|
60
|
+
body: dict[str, Any] = r.json()
|
|
61
|
+
except ValueError:
|
|
62
|
+
body = {}
|
|
61
63
|
reason = body.get("errors") or body.get("msg") or body.get("description")
|
|
62
|
-
raise HTTPError(
|
|
64
|
+
raise HTTPError(
|
|
65
|
+
f"HTTP error occurred: {reason or r.reason} "
|
|
66
|
+
f"(status {r.status_code} for {r.request.method} {r.url})"
|
|
67
|
+
)
|
|
63
68
|
return r
|
|
64
69
|
|
|
65
70
|
def get(self, url: str, *args: Any, **kwargs: Any) -> Response:
|
metafold/simulation/__init__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from metafold.simulation.compression_simulation import (
|
|
2
|
+
BoundaryCondition,
|
|
2
3
|
CompressionSimulation,
|
|
3
4
|
ExperimentBox,
|
|
4
5
|
ExperimentCylinder,
|
|
@@ -14,6 +15,7 @@ from metafold.simulation.compression_simulation import (
|
|
|
14
15
|
ExperimentSupportCylinder,
|
|
15
16
|
ExperimentSupportMesh,
|
|
16
17
|
ExperimentSupportParallelepiped,
|
|
18
|
+
ForceSource,
|
|
17
19
|
ReferenceData,
|
|
18
20
|
SimulationParameters,
|
|
19
21
|
WorkflowStep,
|
|
@@ -21,6 +21,7 @@ from simulation_configurator import (
|
|
|
21
21
|
Contact,
|
|
22
22
|
Mpm,
|
|
23
23
|
)
|
|
24
|
+
from simulation_configurator.element import CompositeElement, Element
|
|
24
25
|
from simulation_configurator.grid import Face
|
|
25
26
|
from simulation_configurator.shapes import Box, File, Cylinder, Parallelepiped
|
|
26
27
|
from plyfile import PlyData, PlyElement
|
|
@@ -71,6 +72,68 @@ ZERO_VELOCITY = [
|
|
|
71
72
|
]
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
class BoundaryCondition(Enum):
|
|
76
|
+
SYMMETRIC = "symmetric" # symmetry plane (acts as frictionless wall)
|
|
77
|
+
VELOCITY_DIRICHLET = "velocity_dirichlet" # velocity fixed to zero at the face
|
|
78
|
+
VELOCITY_NEUMANN = "velocity_neumann" # zero velocity gradient (free face)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
DEFAULT_BOUNDARY_CONDITIONS = {
|
|
82
|
+
"x-": BoundaryCondition.SYMMETRIC,
|
|
83
|
+
"x+": BoundaryCondition.SYMMETRIC,
|
|
84
|
+
"y-": BoundaryCondition.SYMMETRIC,
|
|
85
|
+
"y+": BoundaryCondition.SYMMETRIC,
|
|
86
|
+
"z-": BoundaryCondition.VELOCITY_DIRICHLET,
|
|
87
|
+
"z+": BoundaryCondition.VELOCITY_DIRICHLET,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _velocity_neumann_face(side: str):
|
|
92
|
+
# simulation_configurator.Face only provides fixed (symmetry) and unfixed
|
|
93
|
+
# (velocity Dirichlet) faces; build the Neumann variant here until it can
|
|
94
|
+
# move into a simulation-configurator release.
|
|
95
|
+
face = CompositeElement("Face", {"side": side})
|
|
96
|
+
bc_type = CompositeElement(
|
|
97
|
+
"BCType",
|
|
98
|
+
{"id": "all", "label": "Velocity", "var": "Neumann"},
|
|
99
|
+
)
|
|
100
|
+
bc_type.add(Element("value", data="[0.0, 0.0, 0.0]"))
|
|
101
|
+
face.add(bc_type)
|
|
102
|
+
return face
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
_FACE_BUILDERS = {
|
|
106
|
+
BoundaryCondition.SYMMETRIC: Face.fixed,
|
|
107
|
+
BoundaryCondition.VELOCITY_DIRICHLET: Face.unfixed,
|
|
108
|
+
BoundaryCondition.VELOCITY_NEUMANN: _velocity_neumann_face,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _normalize_boundary_conditions(
|
|
113
|
+
boundary_conditions: dict,
|
|
114
|
+
) -> dict[str, BoundaryCondition]:
|
|
115
|
+
"""Merge per-face boundary conditions over the defaults.
|
|
116
|
+
|
|
117
|
+
Values may be BoundaryCondition members or their string values (as parsed
|
|
118
|
+
from an experiment manifest). Faces not present keep their default.
|
|
119
|
+
"""
|
|
120
|
+
unknown = set(boundary_conditions) - set(DEFAULT_BOUNDARY_CONDITIONS)
|
|
121
|
+
if unknown:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"Unknown boundary condition face(s) {sorted(unknown)}; "
|
|
124
|
+
f"expected faces {sorted(DEFAULT_BOUNDARY_CONDITIONS)}"
|
|
125
|
+
)
|
|
126
|
+
resolved = dict(DEFAULT_BOUNDARY_CONDITIONS)
|
|
127
|
+
for side, bc in boundary_conditions.items():
|
|
128
|
+
resolved[side] = BoundaryCondition(bc) if isinstance(bc, str) else bc
|
|
129
|
+
return resolved
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ForceSource(Enum):
|
|
133
|
+
BOUNDARY_FORCE = "boundary_force" # uses boundary_force_zminus (default)
|
|
134
|
+
RIGID_REACTION_FORCE = "rigid_reaction_force" # uses rigid reaction force of force_source_part
|
|
135
|
+
|
|
136
|
+
|
|
74
137
|
@dataclass
|
|
75
138
|
class SimulationParameters:
|
|
76
139
|
init_time: float = 0.0
|
|
@@ -89,6 +152,15 @@ class SimulationParameters:
|
|
|
89
152
|
extra_cells: list[int] = field(default_factory=lambda: [1, 1, 1])
|
|
90
153
|
patches: list[int] = field(default_factory=lambda: [4, 2, 2])
|
|
91
154
|
|
|
155
|
+
# Per-face boundary conditions; faces omitted here use the default.
|
|
156
|
+
boundary_conditions: dict[str, BoundaryCondition] = field(
|
|
157
|
+
default_factory=lambda: dict(DEFAULT_BOUNDARY_CONDITIONS)
|
|
158
|
+
)
|
|
159
|
+
force_source: ForceSource = ForceSource.BOUNDARY_FORCE
|
|
160
|
+
# Part whose rigid_reaction_force feeds force-displacement when
|
|
161
|
+
# force_source is RIGID_REACTION_FORCE. Empty string means the piston.
|
|
162
|
+
force_source_part: str = ""
|
|
163
|
+
|
|
92
164
|
|
|
93
165
|
@dataclass
|
|
94
166
|
class ExperimentPart:
|
|
@@ -506,6 +578,25 @@ class CompressionSimulation:
|
|
|
506
578
|
p.restore_from_state_dict(saved)
|
|
507
579
|
break
|
|
508
580
|
|
|
581
|
+
@staticmethod
|
|
582
|
+
def cancel_active_workflows(client: MetafoldClient, project_id: str) -> list[str]:
|
|
583
|
+
"""Cancel all in-flight (pending/started) workflows for a project.
|
|
584
|
+
|
|
585
|
+
Returns the number of workflows cancelled. Used when re-running an
|
|
586
|
+
experiment on an existing project, whose previous results we're about
|
|
587
|
+
to overwrite anyway.
|
|
588
|
+
"""
|
|
589
|
+
cancelled = []
|
|
590
|
+
for state in ("pending", "started"):
|
|
591
|
+
for wf in client.workflows.list(q=f"state:{state}", project_id=project_id):
|
|
592
|
+
try:
|
|
593
|
+
client.workflows.cancel(wf.id, project_id=project_id)
|
|
594
|
+
cancelled.append(wf.id)
|
|
595
|
+
except HTTPError:
|
|
596
|
+
# Workflow already finished/uncancellable — ignore and move on.
|
|
597
|
+
pass
|
|
598
|
+
return cancelled
|
|
599
|
+
|
|
509
600
|
@staticmethod
|
|
510
601
|
def find_or_create_project(
|
|
511
602
|
project_name: str,
|
|
@@ -514,12 +605,17 @@ class CompressionSimulation:
|
|
|
514
605
|
client_secret: Optional[str] = None,
|
|
515
606
|
auth_domain: str = "metafold3d.us.auth0.com",
|
|
516
607
|
base_url: str = "https://api.metafold3d.com/",
|
|
608
|
+
cancel_existing_workflows: bool = True,
|
|
517
609
|
) -> str:
|
|
518
610
|
"""Find an existing project by name or create a new one.
|
|
519
611
|
|
|
520
612
|
Supply either access_token (server context — never reads from env) or
|
|
521
613
|
client_id + client_secret (local/programmable use, reads from env via
|
|
522
614
|
setup_client). base_url must match the audience the token was issued for.
|
|
615
|
+
|
|
616
|
+
When cancel_existing_workflows is True and an existing project is
|
|
617
|
+
reused, its running workflows are cancelled first.
|
|
618
|
+
|
|
523
619
|
Returns the project_id string.
|
|
524
620
|
"""
|
|
525
621
|
client = MetafoldClient(
|
|
@@ -534,7 +630,10 @@ class CompressionSimulation:
|
|
|
534
630
|
except HTTPError:
|
|
535
631
|
existing = []
|
|
536
632
|
if existing:
|
|
537
|
-
|
|
633
|
+
project_id = existing[0].id
|
|
634
|
+
if cancel_existing_workflows:
|
|
635
|
+
CompressionSimulation.cancel_active_workflows(client, project_id)
|
|
636
|
+
return project_id
|
|
538
637
|
created = client.projects.create(
|
|
539
638
|
project_name,
|
|
540
639
|
access=Access.PRIVATE,
|
|
@@ -775,17 +874,28 @@ class CompressionSimulation:
|
|
|
775
874
|
|
|
776
875
|
grid_min = np.array(grid_patch["offset"], dtype=np.float32) * 1e-3
|
|
777
876
|
grid_max = np.array(grid_patch["size"], dtype=np.float32) * 1e-3 + grid_min
|
|
778
|
-
grid_min[:2] -= self.simulation_parameters.margin_xy
|
|
779
|
-
grid_max[:2] += self.simulation_parameters.margin_xy
|
|
780
|
-
grid_max[2] += self.simulation_parameters.margin_z
|
|
781
877
|
|
|
782
|
-
# Expand grid to contain every
|
|
878
|
+
# Expand grid to contain every part's bounding box, so nothing is
|
|
879
|
+
# clipped (e.g. a support/outsole extending below the representative
|
|
880
|
+
# part). Primitives report bounds via get_bounds() in metres, mesh
|
|
881
|
+
# parts via their sampled patch (offset/size in mm)
|
|
783
882
|
for info in self.part_infos:
|
|
784
883
|
part = info.part
|
|
785
884
|
if isinstance(part, ExperimentPrimitive):
|
|
786
885
|
pmin, pmax = part.get_bounds()
|
|
787
|
-
|
|
788
|
-
|
|
886
|
+
elif info.patch:
|
|
887
|
+
pmin = np.array(info.patch["offset"], dtype=np.float32) / 1000.0
|
|
888
|
+
pmax = np.array(info.patch["size"], dtype=np.float32) / 1000.0 + pmin
|
|
889
|
+
else:
|
|
890
|
+
continue
|
|
891
|
+
grid_min = np.minimum(grid_min, pmin)
|
|
892
|
+
grid_max = np.maximum(grid_max, pmax)
|
|
893
|
+
|
|
894
|
+
# Apply margins after all parts are included. Note z- gets no margin
|
|
895
|
+
# since the bottom of the grid is the support boundary the stack sits on
|
|
896
|
+
grid_min[:2] -= self.simulation_parameters.margin_xy
|
|
897
|
+
grid_max[:2] += self.simulation_parameters.margin_xy
|
|
898
|
+
grid_max[2] += self.simulation_parameters.margin_z
|
|
789
899
|
|
|
790
900
|
grid_size = grid_max - grid_min
|
|
791
901
|
spacing = (
|
|
@@ -807,10 +917,11 @@ class CompressionSimulation:
|
|
|
807
917
|
}
|
|
808
918
|
)
|
|
809
919
|
bcs = BoundaryConditions()
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
920
|
+
boundary_conditions = _normalize_boundary_conditions(
|
|
921
|
+
self.simulation_parameters.boundary_conditions
|
|
922
|
+
)
|
|
923
|
+
for side, bc in boundary_conditions.items():
|
|
924
|
+
bcs._sub_elements[side] = _FACE_BUILDERS[bc](side)
|
|
814
925
|
grid.add(bcs)
|
|
815
926
|
|
|
816
927
|
store = GeometryStore()
|
|
@@ -867,16 +978,18 @@ class CompressionSimulation:
|
|
|
867
978
|
store.add(geometry_element)
|
|
868
979
|
|
|
869
980
|
outputs_indices_key = ",".join(str(i) for i in range(len(self.part_infos)))
|
|
981
|
+
all_outputs = [
|
|
982
|
+
"boundary_force_zminus",
|
|
983
|
+
"boundary_force_zplus",
|
|
984
|
+
"kinetic_energy",
|
|
985
|
+
"strain_energy",
|
|
986
|
+
]
|
|
987
|
+
if self.simulation_parameters.force_source == ForceSource.RIGID_REACTION_FORCE:
|
|
988
|
+
all_outputs.append("rigid_reaction_force")
|
|
870
989
|
archive = Archive(
|
|
871
990
|
outputs={
|
|
872
991
|
outputs_indices_key: ["deformation_measure", "position", "stress"],
|
|
873
|
-
"all":
|
|
874
|
-
"boundary_force_zminus",
|
|
875
|
-
"boundary_force_zplus",
|
|
876
|
-
"rigid_reaction_force_0",
|
|
877
|
-
"kinetic_energy",
|
|
878
|
-
"strain_energy",
|
|
879
|
-
],
|
|
992
|
+
"all": all_outputs,
|
|
880
993
|
},
|
|
881
994
|
output_interval=self.simulation_parameters.output_int,
|
|
882
995
|
)
|
|
@@ -1052,6 +1165,22 @@ class CompressionSimulation:
|
|
|
1052
1165
|
)
|
|
1053
1166
|
return piston_info
|
|
1054
1167
|
|
|
1168
|
+
def _get_force_source_info(self):
|
|
1169
|
+
"""PartInfo of the body whose rigid_reaction_force is measured."""
|
|
1170
|
+
name = self.simulation_parameters.force_source_part
|
|
1171
|
+
if name:
|
|
1172
|
+
info = self.get_part_info(name)
|
|
1173
|
+
if info is None:
|
|
1174
|
+
raise RuntimeError(f"Unknown force_source_part: {name}")
|
|
1175
|
+
return info
|
|
1176
|
+
piston_info = self._get_piston_info()
|
|
1177
|
+
if piston_info is None:
|
|
1178
|
+
raise RuntimeError(
|
|
1179
|
+
"force_source RIGID_REACTION_FORCE requires a piston part "
|
|
1180
|
+
"or an explicit force_source_part"
|
|
1181
|
+
)
|
|
1182
|
+
return piston_info
|
|
1183
|
+
|
|
1055
1184
|
def _add_to_workflow_von_mises_stress(self, part_infos: list[PartInfo]):
|
|
1056
1185
|
self._add_to_workflow_postprocess(
|
|
1057
1186
|
part_infos, "compress", "von-mises-stress", "cauchy_stress"
|
|
@@ -1065,9 +1194,12 @@ class CompressionSimulation:
|
|
|
1065
1194
|
def _add_to_workflow_force_displacement(self, part_infos: list[PartInfo]):
|
|
1066
1195
|
job_name_base = "force-displacement"
|
|
1067
1196
|
self._add_to_workflow_postprocess(part_infos, "compress", job_name_base)
|
|
1068
|
-
self.
|
|
1069
|
-
|
|
1070
|
-
|
|
1197
|
+
if self.simulation_parameters.force_source == ForceSource.RIGID_REACTION_FORCE:
|
|
1198
|
+
source = self._get_force_source_info()
|
|
1199
|
+
keys = [f"/material{source.material_index}/rigid_reaction_force"]
|
|
1200
|
+
else:
|
|
1201
|
+
keys = ["/boundary_force_zminus"]
|
|
1202
|
+
self.workflow_params[f"{job_name_base}.keys"] = json.dumps(keys)
|
|
1071
1203
|
self.workflow_params[f"{job_name_base}.method"] = "BoundaryForce"
|
|
1072
1204
|
# Find the piston part — there should be exactly one
|
|
1073
1205
|
piston_info = self._get_piston_info()
|
|
@@ -56,7 +56,19 @@ JSON manifest format
|
|
|
56
56
|
|
|
57
57
|
"simulation": {
|
|
58
58
|
"max_time": 0.04,
|
|
59
|
-
"max_resolution": 512
|
|
59
|
+
"max_resolution": 512,
|
|
60
|
+
|
|
61
|
+
# Force source for force-displacement: "boundary_force" (default) or
|
|
62
|
+
# "rigid_reaction_force". When using rigid_reaction_force, optionally
|
|
63
|
+
# name the part it's measured on (defaults to the piston):
|
|
64
|
+
"force_source": "rigid_reaction_force",
|
|
65
|
+
"force_source_part": "puck",
|
|
66
|
+
|
|
67
|
+
# Per-face boundary conditions. Only the faces you list are overridden;
|
|
68
|
+
# the rest keep their defaults (x/y faces "symmetric", z faces
|
|
69
|
+
# "velocity_dirichlet"). Each value is one of: "symmetric",
|
|
70
|
+
# "velocity_dirichlet", "velocity_neumann".
|
|
71
|
+
"boundary_conditions": {"z-": "symmetric", "y+": "velocity_neumann"}
|
|
60
72
|
},
|
|
61
73
|
|
|
62
74
|
"workflow_steps": [
|
|
@@ -100,6 +112,7 @@ from __future__ import annotations
|
|
|
100
112
|
|
|
101
113
|
import json
|
|
102
114
|
import tempfile
|
|
115
|
+
from enum import Enum
|
|
103
116
|
from pathlib import Path
|
|
104
117
|
from typing import Optional, Union
|
|
105
118
|
from zipfile import ZipFile
|
|
@@ -234,6 +247,13 @@ def _build_simulation_parameters(sim_config: dict) -> SimulationParameters:
|
|
|
234
247
|
obj = params
|
|
235
248
|
for part in parts[:-1]:
|
|
236
249
|
obj = getattr(obj, part)
|
|
250
|
+
# Manifest values for enum fields (e.g. force_source) arrive as plain
|
|
251
|
+
# strings; coerce them to the field's enum type. Non-enum fields (e.g.
|
|
252
|
+
# the boundary_conditions dict) pass through and are normalized later
|
|
253
|
+
# in create_sim_config.
|
|
254
|
+
current = getattr(obj, parts[-1], None)
|
|
255
|
+
if isinstance(current, Enum) and isinstance(value, str):
|
|
256
|
+
value = type(current)(value)
|
|
237
257
|
setattr(obj, parts[-1], value)
|
|
238
258
|
return params
|
|
239
259
|
|
|
@@ -2,19 +2,19 @@ metafold/__init__.py,sha256=fDZ2HCBNDOJfs1zAhCse6h2lOiqxl2uUMJT-JuO0XNE,1899
|
|
|
2
2
|
metafold/api.py,sha256=SrlDgvaqy0Vjf9w67OmmOUs3YAQaIEFHb1rpxvpwFWw,1052
|
|
3
3
|
metafold/assets.py,sha256=41OUQ1wKiYitEZMBRPbFfxtM3m4TYb_5S6wXNEbgi40,4569
|
|
4
4
|
metafold/auth.py,sha256=O4B5vGUr2UmHbw_1BfWqZn-GWvmf_eFpZEpKGgBGrQQ,1079
|
|
5
|
-
metafold/client.py,sha256=
|
|
5
|
+
metafold/client.py,sha256=ZFftCr5gbRFkrxgBK7NYspBQEQnNzj2tWCbjPqxg5gw,3714
|
|
6
6
|
metafold/exceptions.py,sha256=dJhBgbryBhJUf20FT3eU1rjQJQ-aDEZ25g-YCn_TwQk,110
|
|
7
7
|
metafold/jobs.py,sha256=DxpQ9yrN7Th67XyW8UqjAIX0wfhwgPPCA9mNSKG_Z_Y,7029
|
|
8
8
|
metafold/materials.py,sha256=heiK_kpU3YhKMPy1P9Zfds8yyUib0yqq3zethufchOc,10814
|
|
9
9
|
metafold/projects.py,sha256=4APFtJQjycFhlIHCWTrOAuU3CLX8drSlgPz8lhVXhzI,6295
|
|
10
10
|
metafold/utils.py,sha256=DnsW1Xr9mLpVHBNdPrdxNLLcjxSNMkWVvr7cH7UTAxY,1174
|
|
11
11
|
metafold/workflows.py,sha256=Mhv3n3ChUHw1Vaj3q8HNDbOgljn03AF6WslZfXzexsI,7802
|
|
12
|
-
metafold/simulation/__init__.py,sha256=
|
|
12
|
+
metafold/simulation/__init__.py,sha256=tUW4PfyWnI6UFvIR__JKUD2FjNTM74fSeKuee7Ewjfo,816
|
|
13
13
|
metafold/simulation/compression_experiment.py,sha256=Dc7EKPAmKEZsNcH5vnKuj3dQcFFeh0hsus9CIA0Ycv4,14221
|
|
14
|
-
metafold/simulation/compression_simulation.py,sha256
|
|
15
|
-
metafold/simulation/run_experiment.py,sha256=
|
|
16
|
-
metafold-0.12.
|
|
17
|
-
metafold-0.12.
|
|
18
|
-
metafold-0.12.
|
|
19
|
-
metafold-0.12.
|
|
20
|
-
metafold-0.12.
|
|
14
|
+
metafold/simulation/compression_simulation.py,sha256=akAEJhYQrfWATnVXsOaa-zfx-ufWykEYEjEpR_ov0l4,103193
|
|
15
|
+
metafold/simulation/run_experiment.py,sha256=jj5tZgOhEdPGZZcJFPL_pELfcFlosTMlWMRxgtur9Uk,14748
|
|
16
|
+
metafold-0.12.dev4.dist-info/licenses/LICENSE,sha256=LejZXzGwe9t0Ezk6g0bRmWUuFSI9vQSk1wJAc1NrrhE,1059
|
|
17
|
+
metafold-0.12.dev4.dist-info/METADATA,sha256=mqzqN_sPPpFDIspJiBtnRi8PqkxuZEaDGVCXAfokWdU,3495
|
|
18
|
+
metafold-0.12.dev4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
19
|
+
metafold-0.12.dev4.dist-info/top_level.txt,sha256=0dvwa2N6gvl2x4T9c62V4MbYD2soFedI6hm3-lWgyew,9
|
|
20
|
+
metafold-0.12.dev4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|