pyfebio 0.1.1__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.
- pyfebio/__init__.py +45 -0
- pyfebio/_types.py +95 -0
- pyfebio/boundary.py +119 -0
- pyfebio/constraints.py +41 -0
- pyfebio/contact.py +172 -0
- pyfebio/control.py +76 -0
- pyfebio/discrete.py +40 -0
- pyfebio/globals.py +12 -0
- pyfebio/include.py +5 -0
- pyfebio/initial.py +27 -0
- pyfebio/loaddata.py +51 -0
- pyfebio/loads.py +55 -0
- pyfebio/material.py +1191 -0
- pyfebio/mesh.py +354 -0
- pyfebio/meshadaptor.py +159 -0
- pyfebio/meshdata.py +52 -0
- pyfebio/meshdomains.py +65 -0
- pyfebio/model.py +145 -0
- pyfebio/module.py +14 -0
- pyfebio/output.py +298 -0
- pyfebio/py.typed +0 -0
- pyfebio/rigid.py +275 -0
- pyfebio/step.py +28 -0
- pyfebio-0.1.1.dist-info/METADATA +346 -0
- pyfebio-0.1.1.dist-info/RECORD +26 -0
- pyfebio-0.1.1.dist-info/WHEEL +4 -0
pyfebio/mesh.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import meshio
|
|
4
|
+
import numpy as np
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from pydantic_xml import BaseXmlModel, attr, element
|
|
7
|
+
|
|
8
|
+
from ._types import (
|
|
9
|
+
StringFloatVec3,
|
|
10
|
+
StringUIntVec,
|
|
11
|
+
StringUIntVec2,
|
|
12
|
+
StringUIntVec3,
|
|
13
|
+
StringUIntVec4,
|
|
14
|
+
StringUIntVec6,
|
|
15
|
+
StringUIntVec8,
|
|
16
|
+
StringUIntVec9,
|
|
17
|
+
StringUIntVec10,
|
|
18
|
+
StringUIntVec15,
|
|
19
|
+
StringUIntVec20,
|
|
20
|
+
StringUIntVec27,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
SolidFEBioElementType = Literal["tet4", "tet10", "tet15", "hex8", "hex20", "hex27", "penta6"]
|
|
24
|
+
ShellFEBioElementType = Literal["tri3", "tri6", "quad4", "quad8", "quad9", "q4ans", "q4eas"]
|
|
25
|
+
BeamFEBioElementType = Literal["line2", "line3"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Node(BaseXmlModel, tag="node", validate_assignment=True):
|
|
29
|
+
text: StringFloatVec3 = Field(default="0.0, 0.0, 0.0")
|
|
30
|
+
id: int = attr()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Nodes(BaseXmlModel, validate_assignment=True):
|
|
34
|
+
name: str = attr(default="")
|
|
35
|
+
all_nodes: list[Node] = element(tag="node", default=[])
|
|
36
|
+
|
|
37
|
+
def add_node(self, new_node: Node):
|
|
38
|
+
self.all_nodes.append(new_node)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Tet4Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
42
|
+
text: StringUIntVec4 = Field(default="1,2,3,4")
|
|
43
|
+
id: int = attr()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Tet10Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
47
|
+
text: StringUIntVec10 = Field(default="1,2,3,4,5,6,7,8,9,10")
|
|
48
|
+
id: int = attr()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Tet15Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
52
|
+
text: StringUIntVec15 = Field(default="1,2,3,4,5,6,7,8,9,10,11,12,13,14,15")
|
|
53
|
+
id: int = attr()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Hex8Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
57
|
+
text: StringUIntVec8 = Field(default="1,2,3,4,5,6,7,8")
|
|
58
|
+
id: int = attr()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Hex20Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
62
|
+
text: StringUIntVec20 = Field(default="1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20")
|
|
63
|
+
id: int = attr()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Hex27Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
67
|
+
text: StringUIntVec27 = Field(default="1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27")
|
|
68
|
+
id: int = attr()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Penta6Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
72
|
+
text: StringUIntVec6 = Field(default="1,2,3,4,5,6")
|
|
73
|
+
id: int = attr()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Tri3Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
77
|
+
text: StringUIntVec3 = Field(default="1,2,3")
|
|
78
|
+
id: int = attr()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Tri6Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
82
|
+
text: StringUIntVec6 = Field(default="1,2,3,4,5,6")
|
|
83
|
+
id: int = attr()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Quad4Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
87
|
+
text: StringUIntVec4 = Field(default="1,2,3,4")
|
|
88
|
+
id: int = attr()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Quad8Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
92
|
+
text: StringUIntVec8 = Field(default="1,2,3,4,5,6,7,8")
|
|
93
|
+
id: int = attr()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Quad9Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
97
|
+
text: StringUIntVec9 = Field(default="1,2,3,4,5,6,7,8,9")
|
|
98
|
+
id: int = attr()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Line2Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
102
|
+
text: StringUIntVec2 = Field(default="1,2")
|
|
103
|
+
id: int = attr()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Line3Element(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
107
|
+
text: StringUIntVec3 = Field(default="1,2,3")
|
|
108
|
+
id: int = attr()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
ElementType = (
|
|
112
|
+
Tet4Element
|
|
113
|
+
| Tet10Element
|
|
114
|
+
| Tet15Element
|
|
115
|
+
| Hex8Element
|
|
116
|
+
| Hex20Element
|
|
117
|
+
| Hex27Element
|
|
118
|
+
| Penta6Element
|
|
119
|
+
| Tri3Element
|
|
120
|
+
| Tri6Element
|
|
121
|
+
| Quad4Element
|
|
122
|
+
| Quad8Element
|
|
123
|
+
| Quad9Element
|
|
124
|
+
| Line2Element
|
|
125
|
+
| Line3Element
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Elements(BaseXmlModel, tag="elements", validate_assignment=True):
|
|
130
|
+
name: str = attr(default="Part")
|
|
131
|
+
type: SolidFEBioElementType | ShellFEBioElementType | BeamFEBioElementType = attr(default="hex8")
|
|
132
|
+
all_elements: list[ElementType] = element(default=[], tag="elem")
|
|
133
|
+
|
|
134
|
+
def add_element(self, new_element: ElementType):
|
|
135
|
+
self.all_elements.append(new_element)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ElementSet(BaseXmlModel, tag="ElementSet", validate_assignment=True):
|
|
139
|
+
name: str = attr(default="")
|
|
140
|
+
text: StringUIntVec
|
|
141
|
+
|
|
142
|
+
def add_element(self, new_element_id: int):
|
|
143
|
+
",".join([self.text, str(new_element_id)])
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class NodeSet(BaseXmlModel, tag="NodeSet", validate_assignment=True):
|
|
147
|
+
name: str = attr(default="")
|
|
148
|
+
text: StringUIntVec
|
|
149
|
+
|
|
150
|
+
def add_node(self, new_node_id: int):
|
|
151
|
+
",".join([self.text, str(new_node_id)])
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Surface(BaseXmlModel, tag="Surface", validate_assignment=True):
|
|
155
|
+
name: str = attr(default="")
|
|
156
|
+
all_tri3: list[Tri3Element] = element(default=[], tag="tri3")
|
|
157
|
+
all_tri6: list[Tri6Element] = element(default=[], tag="tri6")
|
|
158
|
+
all_quad4: list[Quad4Element] = element(default=[], tag="quad4")
|
|
159
|
+
all_quad8: list[Quad8Element] = element(default=[], tag="quad8")
|
|
160
|
+
all_quad9: list[Quad9Element] = element(default=[], tag="quad9")
|
|
161
|
+
|
|
162
|
+
def add_tri3(self, new_tri: Tri3Element):
|
|
163
|
+
self.all_tri3.append(new_tri)
|
|
164
|
+
|
|
165
|
+
def add_tri6(self, new_tri: Tri6Element):
|
|
166
|
+
self.all_tri6.append(new_tri)
|
|
167
|
+
|
|
168
|
+
def add_quad4(self, new_quad: Quad4Element):
|
|
169
|
+
self.all_quad4.append(new_quad)
|
|
170
|
+
|
|
171
|
+
def add_quad8(self, new_quad: Quad8Element):
|
|
172
|
+
self.all_quad8.append(new_quad)
|
|
173
|
+
|
|
174
|
+
def add_quad9(self, new_quad: Quad9Element):
|
|
175
|
+
self.all_quad9.append(new_quad)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class SurfacePair(BaseXmlModel, tag="SurfacePair", validate_assignment=True):
|
|
179
|
+
name: str = attr(default="")
|
|
180
|
+
primary: str = element()
|
|
181
|
+
secondary: str = element()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class DiscreteElement(BaseXmlModel, tag="delem", validate_assignment=True):
|
|
185
|
+
text: StringUIntVec2
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class DiscreteSet(BaseXmlModel, tag="DiscreteSet", validate_assignment=True):
|
|
189
|
+
name: str = attr(default="")
|
|
190
|
+
elements: list[DiscreteElement] = element(default=[])
|
|
191
|
+
|
|
192
|
+
def add_element(self, new_element: DiscreteElement):
|
|
193
|
+
self.elements.append(new_element)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class Mesh(BaseXmlModel, validate_assignment=True):
|
|
197
|
+
nodes: list[Nodes] = element(default=[], tag="Nodes")
|
|
198
|
+
elements: list[Elements] = element(default=[], tag="Elements")
|
|
199
|
+
surfaces: list[Surface] = element(default=[], tag="Surface")
|
|
200
|
+
element_sets: list[ElementSet] = element(default=[], tag="ElementSet")
|
|
201
|
+
node_sets: list[NodeSet] = element(default=[], tag="NodeSet")
|
|
202
|
+
discrete_sets: list[DiscreteSet] = element(default=[], tag="DiscreteSet")
|
|
203
|
+
surface_pairs: list[SurfacePair] = element(default=[], tag="SurfacePair")
|
|
204
|
+
|
|
205
|
+
def add_node_domain(self, new_node_domain: Nodes):
|
|
206
|
+
if not new_node_domain.name:
|
|
207
|
+
new_node_domain.name = f"Part{len(self.nodes) + 1}"
|
|
208
|
+
self.nodes.append(new_node_domain)
|
|
209
|
+
|
|
210
|
+
def add_element_domain(self, new_element_domain: Elements):
|
|
211
|
+
if new_element_domain.name == "Part":
|
|
212
|
+
new_element_domain.name = f"Part{len(self.elements) + 1}"
|
|
213
|
+
self.elements.append(new_element_domain)
|
|
214
|
+
|
|
215
|
+
def add_surface(self, new_surface: Surface):
|
|
216
|
+
if not new_surface.name:
|
|
217
|
+
new_surface.name = f"Surface{len(self.surfaces) + 1}"
|
|
218
|
+
self.surfaces.append(new_surface)
|
|
219
|
+
|
|
220
|
+
def add_element_set(self, new_element_set: ElementSet):
|
|
221
|
+
if not new_element_set.name:
|
|
222
|
+
new_element_set.name = f"ElementSet{len(self.element_sets) + 1}"
|
|
223
|
+
self.element_sets.append(new_element_set)
|
|
224
|
+
|
|
225
|
+
def add_node_set(self, new_node_set: NodeSet):
|
|
226
|
+
if not new_node_set.name:
|
|
227
|
+
new_node_set.name = f"NodeSet{len(self.node_sets) + 1}"
|
|
228
|
+
self.node_sets.append(new_node_set)
|
|
229
|
+
|
|
230
|
+
def add_discrete_set(self, new_discrete_set: DiscreteSet):
|
|
231
|
+
if not new_discrete_set.name:
|
|
232
|
+
new_discrete_set.name = f"DiscreteSet{len(self.discrete_sets) + 1}"
|
|
233
|
+
self.discrete_sets.append(new_discrete_set)
|
|
234
|
+
|
|
235
|
+
def add_surface_pair(self, new_surface_pair: SurfacePair):
|
|
236
|
+
if not new_surface_pair.name:
|
|
237
|
+
new_surface_pair.name = f"SurfacePair{len(self.surface_pairs) + 1}"
|
|
238
|
+
self.surface_pairs.append(new_surface_pair)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
ELEMENT_MAP: dict[str, SolidFEBioElementType | ShellFEBioElementType | BeamFEBioElementType] = {
|
|
242
|
+
"tetra": "tet4",
|
|
243
|
+
"tetra10": "tet10",
|
|
244
|
+
"hexahedron": "hex8",
|
|
245
|
+
"hexahedron20": "hex20",
|
|
246
|
+
"hexahedron27": "hex27",
|
|
247
|
+
"triangle": "tri3",
|
|
248
|
+
"triangle6": "tri6",
|
|
249
|
+
"quad": "quad4",
|
|
250
|
+
"quad8": "quad8",
|
|
251
|
+
"quad9": "quad9",
|
|
252
|
+
"line": "line2",
|
|
253
|
+
"line3": "line3",
|
|
254
|
+
}
|
|
255
|
+
ELEMENT_CLASS_MAP: dict[str, type[ElementType]] = {
|
|
256
|
+
"tet4": Tet4Element,
|
|
257
|
+
"tet10": Tet10Element,
|
|
258
|
+
"hex8": Hex8Element,
|
|
259
|
+
"hex20": Hex20Element,
|
|
260
|
+
"hex27": Hex27Element,
|
|
261
|
+
"tri3": Tri3Element,
|
|
262
|
+
"tri6": Tri6Element,
|
|
263
|
+
"quad4": Quad4Element,
|
|
264
|
+
"quad8": Quad8Element,
|
|
265
|
+
"quad9": Quad9Element,
|
|
266
|
+
"line2": Line2Element,
|
|
267
|
+
"line3": Line3Element,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
EXCLUDE_SET_STR = ("gmsh:bounding_entities",)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def translate_meshio(
|
|
274
|
+
meshobj: meshio.Mesh,
|
|
275
|
+
nodeoffset: int = 0,
|
|
276
|
+
elementoffset: int = 0,
|
|
277
|
+
surfaceoffset: int = 0,
|
|
278
|
+
shell_sets: list[str] | None = None,
|
|
279
|
+
) -> Mesh:
|
|
280
|
+
if shell_sets is None:
|
|
281
|
+
shell_sets = []
|
|
282
|
+
solid_nodes = []
|
|
283
|
+
for key, value in meshobj.cells_dict.items():
|
|
284
|
+
if meshio._mesh.topological_dimension[key] == 3:
|
|
285
|
+
solid_nodes.extend(np.unique(value.ravel()).tolist())
|
|
286
|
+
solid_nodes = set(solid_nodes)
|
|
287
|
+
|
|
288
|
+
make_element = {}
|
|
289
|
+
for key, values in meshobj.cells_dict.items():
|
|
290
|
+
make_element[key] = []
|
|
291
|
+
if meshio._mesh.topological_dimension[key] == 2:
|
|
292
|
+
for element in values:
|
|
293
|
+
make_element[key].append(bool(set(np.unique(element.ravel())).difference(solid_nodes)))
|
|
294
|
+
else:
|
|
295
|
+
make_element[key].extend([True] * len(values))
|
|
296
|
+
|
|
297
|
+
febio_mesh = Mesh()
|
|
298
|
+
nodes_object = Nodes()
|
|
299
|
+
for i, node in enumerate(meshobj.points):
|
|
300
|
+
nodes_object.add_node(Node(id=i + 1 + nodeoffset, text=",".join(map(str, node))))
|
|
301
|
+
num_elements = 0
|
|
302
|
+
num_surface_elements = 0
|
|
303
|
+
hex27_reorder = [2, 6, 7, 3, 1, 5, 4, 0, 18, 14, 19, 10, 17, 12, 16, 8, 9, 13, 15, 11]
|
|
304
|
+
hex27_reorder.extend([21, 25, 20, 24, 23, 22, 26])
|
|
305
|
+
for name, members in meshobj.cell_sets_dict.items():
|
|
306
|
+
if any([exclude in name.lower() for exclude in EXCLUDE_SET_STR]):
|
|
307
|
+
continue
|
|
308
|
+
shell_set = name in shell_sets
|
|
309
|
+
for member, offsets in members.items():
|
|
310
|
+
if len(members.keys()) > 1:
|
|
311
|
+
set_name = f"{name}_{ELEMENT_MAP[member]}"
|
|
312
|
+
else:
|
|
313
|
+
set_name = name
|
|
314
|
+
etype = ELEMENT_MAP[member]
|
|
315
|
+
if shell_set or np.array(make_element[member])[offsets].all():
|
|
316
|
+
elements_object = Elements(name=set_name, type=etype)
|
|
317
|
+
for offset in offsets:
|
|
318
|
+
element = meshobj.cells_dict[member][offset]
|
|
319
|
+
if etype == "hex27":
|
|
320
|
+
element = element[hex27_reorder]
|
|
321
|
+
|
|
322
|
+
num_elements += 1
|
|
323
|
+
elements_object.add_element(
|
|
324
|
+
ELEMENT_CLASS_MAP[etype](
|
|
325
|
+
id=num_elements + elementoffset,
|
|
326
|
+
text=",".join(map(str, element + 1)),
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
febio_mesh.elements.append(elements_object)
|
|
330
|
+
else:
|
|
331
|
+
surface_object = Surface(name=set_name)
|
|
332
|
+
fn_map = {
|
|
333
|
+
"tri3": surface_object.add_tri3,
|
|
334
|
+
"tri6": surface_object.add_tri6,
|
|
335
|
+
"quad4": surface_object.add_quad4,
|
|
336
|
+
"quad8": surface_object.add_quad8,
|
|
337
|
+
"quad9": surface_object.add_quad9,
|
|
338
|
+
}
|
|
339
|
+
node_set = []
|
|
340
|
+
for offset in offsets:
|
|
341
|
+
num_surface_elements += 1
|
|
342
|
+
element = meshobj.cells_dict[member][offset]
|
|
343
|
+
fn_map[ELEMENT_MAP[member]](
|
|
344
|
+
ELEMENT_CLASS_MAP[etype](
|
|
345
|
+
id=num_surface_elements + surfaceoffset,
|
|
346
|
+
text=",".join(map(str, element + 1)),
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
node_set.extend((element + 1).tolist())
|
|
350
|
+
node_sets = sorted(set(node_set))
|
|
351
|
+
febio_mesh.node_sets.append(NodeSet(name=set_name, text=",".join(map(str, node_sets))))
|
|
352
|
+
febio_mesh.surfaces.append(surface_object)
|
|
353
|
+
febio_mesh.nodes.append(nodes_object)
|
|
354
|
+
return febio_mesh
|
pyfebio/meshadaptor.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic_xml import BaseXmlModel, attr, element
|
|
4
|
+
|
|
5
|
+
from ._types import StringFloatVec2, StringUIntVec
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MaxVariableCriterion(BaseXmlModel, tag="criterion"):
|
|
9
|
+
type: Literal["max_variable"] = attr(default="max_variable", frozen=True)
|
|
10
|
+
dof: int = element(default=-1)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ElementSelectionCriterion(BaseXmlModel, tag="criterion"):
|
|
14
|
+
type: Literal["element_selection"] = attr(default="element_selection", frozen=True)
|
|
15
|
+
element_list: StringUIntVec = element()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ContactGapCriterion(BaseXmlModel, tag="criterion"):
|
|
19
|
+
type: Literal["contact gap"] = attr(default="contact gap", frozen=True)
|
|
20
|
+
gap: float = element(default=0.0)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StressCriterion(BaseXmlModel, tag="criterion"):
|
|
24
|
+
type: Literal["stress"] = attr(default="stress", frozen=True)
|
|
25
|
+
metric: Literal[0, 1] = element(default=0, description="0=effective stress, 1=max principal stress")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MathCriterion(BaseXmlModel, tag="criterion"):
|
|
29
|
+
type: Literal["math"] = attr(default="math", frozen=True)
|
|
30
|
+
math: str = element(default="1")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DamageCriterion(BaseXmlModel, tag="criterion"):
|
|
34
|
+
type: Literal["damage"] = attr(default="damage", frozen=True)
|
|
35
|
+
damage: float = element(default=0.0)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MinMaxFilterCriterion(BaseXmlModel, tag="criterion"):
|
|
39
|
+
type: Literal["min-max filter"] = attr(default="min-max filter", frozen=True)
|
|
40
|
+
min: float = element(default=-1e37)
|
|
41
|
+
max: float = element(default=1e37)
|
|
42
|
+
clamp: Literal[0, 1] = element(default=0)
|
|
43
|
+
data: ContactGapCriterion | StressCriterion | DamageCriterion | MathCriterion = element(default=StressCriterion(), tag="data")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RelativeErrorCriterion(BaseXmlModel, tag="criterion"):
|
|
47
|
+
type: Literal["relative error"] = attr(default="relative error", frozen=True)
|
|
48
|
+
error: Literal[0] | float = element(default=0)
|
|
49
|
+
data: ContactGapCriterion | StressCriterion | DamageCriterion | MathCriterion = element(default=StressCriterion(), tag="data")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
CriterionType = (
|
|
53
|
+
MaxVariableCriterion
|
|
54
|
+
| ElementSelectionCriterion
|
|
55
|
+
| ContactGapCriterion
|
|
56
|
+
| StressCriterion
|
|
57
|
+
| MathCriterion
|
|
58
|
+
| DamageCriterion
|
|
59
|
+
| MinMaxFilterCriterion
|
|
60
|
+
| RelativeErrorCriterion
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MMGStepSizeFunction(BaseXmlModel, tag="size_function"):
|
|
65
|
+
type: Literal["step"] = attr(default="step", frozen=True)
|
|
66
|
+
x0: float = element(default=0.0)
|
|
67
|
+
left_val: float = element(default=0.0)
|
|
68
|
+
right_val: float = element(default=1.0)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MMGConstantSizeFunction(BaseXmlModel, tag="size_function"):
|
|
72
|
+
type: Literal["const"] = attr(default="const", frozen=True)
|
|
73
|
+
value: float = element(default=0.0)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class MMGLinearRampSizeFunction(BaseXmlModel, tag="size_function"):
|
|
77
|
+
type: Literal["linear ramp"] = attr(default="linear ramp", frozen=True)
|
|
78
|
+
slope: float = element(default=1.0)
|
|
79
|
+
intercept: float = element(default=0.0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class MMGMathSizeFunction(BaseXmlModel, tag="size_function"):
|
|
83
|
+
type: Literal["math"] = attr(default="math", frozen=True)
|
|
84
|
+
math: str = element(default="1")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CurvePoints(BaseXmlModel, tag="points"):
|
|
88
|
+
type: Literal["curve"] = attr(default="curve", frozen=True)
|
|
89
|
+
pt: list[StringFloatVec2] = element(default=["0,0.25", "1,1"])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class MMGPointSizeFunction(BaseXmlModel, tag="size_function"):
|
|
93
|
+
type: Literal["point"] = attr(default="point", frozen=True)
|
|
94
|
+
interpolate: Literal["linear", "smooth", "step"] = element(default="linear")
|
|
95
|
+
extend: Literal["constant", "extrapolate", "repeat", "repeat offset"] = element(default="constant")
|
|
96
|
+
points: CurvePoints = element(default=CurvePoints())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
MMGSizeFunctionType = MMGStepSizeFunction | MMGConstantSizeFunction | MMGLinearRampSizeFunction | MMGMathSizeFunction | MMGPointSizeFunction
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ErosionAdaptor(BaseXmlModel, tag="mesh_adaptor"):
|
|
103
|
+
type: Literal["erosion"] = attr(default="erosion", frozen=True)
|
|
104
|
+
elem_set: str | None = attr(default=None)
|
|
105
|
+
max_iters: int = element(default=1)
|
|
106
|
+
max_elements: int = element(default=3)
|
|
107
|
+
remove_islands: Literal[0, 1] = element(default=0)
|
|
108
|
+
sort: Literal[0, 1] = element(default=1)
|
|
109
|
+
erode_surfaces: Literal["no", "yes", "grow", "reconstruct"] = element(default="no")
|
|
110
|
+
criterion: CriterionType = element(default=MinMaxFilterCriterion(data=StressCriterion()))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class MMGRemeshAdaptor(BaseXmlModel, tag="mesh_adaptor"):
|
|
114
|
+
type: Literal["mmg_remesh"] = attr(default="mmg_remesh", frozen=True)
|
|
115
|
+
elem_set: str | None = attr(default=None)
|
|
116
|
+
max_iters: int = element(default=1)
|
|
117
|
+
max_elements: int = element(default=-1)
|
|
118
|
+
min_element_size: float = element(default=0.1)
|
|
119
|
+
hausdorff: float = element(default=0.01)
|
|
120
|
+
gradation: float = element(default=1.3, gt=1.0)
|
|
121
|
+
mesh_coarsen: Literal[0, 1] = element(default=0)
|
|
122
|
+
normalize_data: Literal[0, 1] = element(default=0)
|
|
123
|
+
relative_size: Literal[0, 1] = element(default=1)
|
|
124
|
+
criterion: CriterionType = element(default=MinMaxFilterCriterion(data=StressCriterion()))
|
|
125
|
+
size_function: MMGSizeFunctionType | None = element(default=None)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class HexRefine2dAdaptor(BaseXmlModel, tag="mesh_adaptor"):
|
|
129
|
+
type: Literal["hex_refine2d"] = attr(default="hex_refine2d", frozen=True)
|
|
130
|
+
elem_set: str | None = attr(default=None)
|
|
131
|
+
max_iters: int = element(default=1)
|
|
132
|
+
max_elements: int = element(default=-1)
|
|
133
|
+
max_elem_refine: int = element(default=0)
|
|
134
|
+
max_value: float = element(default=0.01)
|
|
135
|
+
nnc: int = element(default=8)
|
|
136
|
+
nsdim: int = element(default=3)
|
|
137
|
+
criterion: CriterionType = element(default=RelativeErrorCriterion(data=StressCriterion()))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class HexRefineAdaptor(BaseXmlModel, tag="mesh_adaptor"):
|
|
141
|
+
type: Literal["hex_refine"] = attr(default="hex_refine", frozen=True)
|
|
142
|
+
elem_set: str | None = attr(default=None)
|
|
143
|
+
max_iters: int = element(default=1)
|
|
144
|
+
max_elements: int = element(default=-1)
|
|
145
|
+
max_elem_refine: int = element(default=0)
|
|
146
|
+
max_value: float = element(default=0.01)
|
|
147
|
+
nnc: int = element(default=8)
|
|
148
|
+
nsdim: int = element(default=3)
|
|
149
|
+
criterion: CriterionType = element(default=RelativeErrorCriterion(data=StressCriterion()))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
AdaptorType = ErosionAdaptor | MMGRemeshAdaptor | HexRefine2dAdaptor | HexRefineAdaptor
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class MeshAdaptor(BaseXmlModel, tag="MeshAdaptor"):
|
|
156
|
+
all_adaptors: list[AdaptorType] = element(default=[])
|
|
157
|
+
|
|
158
|
+
def add_adaptor(self, adaptor: AdaptorType):
|
|
159
|
+
self.all_adaptors.append(adaptor)
|
pyfebio/meshdata.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic_xml import BaseXmlModel, attr, element
|
|
4
|
+
|
|
5
|
+
from ._types import (
|
|
6
|
+
StringFloatVec3,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NodeDataNode(BaseXmlModel, tag="node", validate_assignment=True):
|
|
11
|
+
lid: int = attr(ge=1)
|
|
12
|
+
text: float | StringFloatVec3
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NodeData(BaseXmlModel, validate_assignment=True):
|
|
16
|
+
name: str = attr()
|
|
17
|
+
node_set: str = attr()
|
|
18
|
+
data_type: Literal["scalar", "vec3"] = attr()
|
|
19
|
+
all_nodes: list[NodeDataNode] = element(default=[], tag="node")
|
|
20
|
+
|
|
21
|
+
def add_node(self, new_node: NodeDataNode):
|
|
22
|
+
self.all_nodes.append(new_node)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ElementDataElement(BaseXmlModel, tag="elem", validate_assignment=True):
|
|
26
|
+
lid: int = attr(ge=1)
|
|
27
|
+
text: float | StringFloatVec3
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ElementData(BaseXmlModel, validate_assignment=True):
|
|
31
|
+
name: str = attr()
|
|
32
|
+
elem_set: str = attr()
|
|
33
|
+
data_type: Literal["scalar", "vec3"] = attr()
|
|
34
|
+
all_elements: list[ElementDataElement] = element(default=[], tag="elem")
|
|
35
|
+
|
|
36
|
+
def add_element(self, new_element: ElementDataElement):
|
|
37
|
+
self.all_elements.append(new_element)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SurfaceData(BaseXmlModel, validate_assignment=True):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MeshData(BaseXmlModel, validate_assignment=True):
|
|
45
|
+
element_data: list[ElementData] = element(default=[], tag="ElementData")
|
|
46
|
+
node_data: list[NodeData] = element(default=[], tag="NodeData")
|
|
47
|
+
|
|
48
|
+
def add_element_data(self, new_element_data: ElementData):
|
|
49
|
+
self.element_data.append(new_element_data)
|
|
50
|
+
|
|
51
|
+
def add_node_data(self, new_node_data: NodeData):
|
|
52
|
+
self.node_data.append(new_node_data)
|
pyfebio/meshdomains.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic_xml import BaseXmlModel, attr, element
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SolidDomain(BaseXmlModel, validate_assignment=True):
|
|
7
|
+
name: str = attr(default="SolidPart")
|
|
8
|
+
type: Literal["elastic-solid", "three-field-solid", "rigid-solid", "udg-hex", "sri-solid", "remodelling-solid", "ut4-solid"] | None = (
|
|
9
|
+
attr(default=None)
|
|
10
|
+
)
|
|
11
|
+
elem_type: (
|
|
12
|
+
Literal[
|
|
13
|
+
"HEX8G6",
|
|
14
|
+
"HEX8G8",
|
|
15
|
+
"HEX20G8",
|
|
16
|
+
"TET4G1",
|
|
17
|
+
"TET4G4",
|
|
18
|
+
"TET10G4",
|
|
19
|
+
"TET10G8",
|
|
20
|
+
"TET10GL11",
|
|
21
|
+
"TET15G8",
|
|
22
|
+
"TET15G11",
|
|
23
|
+
"TET15G15",
|
|
24
|
+
"PENTA15G8",
|
|
25
|
+
]
|
|
26
|
+
| None
|
|
27
|
+
) = attr(default=None)
|
|
28
|
+
mat: str = attr(default="material")
|
|
29
|
+
alpha: float | None = element(default=None)
|
|
30
|
+
iso_stab: Literal[0, 1] | None = element(default=None)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ShellDomain(BaseXmlModel, validate_assignment=True):
|
|
34
|
+
name: str = attr(default="ShellPart")
|
|
35
|
+
type: Literal[
|
|
36
|
+
"elastic-shell",
|
|
37
|
+
"three-field-shell",
|
|
38
|
+
"rigid-shell",
|
|
39
|
+
"elastic-shell-old",
|
|
40
|
+
"elastic-shell-eas",
|
|
41
|
+
"elastic-shell-ans",
|
|
42
|
+
] = attr(default="elastic-shell")
|
|
43
|
+
mat: str = attr(default="material")
|
|
44
|
+
shell_thickness: float = element(default=0.01)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BeamDomain(BaseXmlModel, validate_assignment=True):
|
|
48
|
+
name: str = attr(default="BeamPart")
|
|
49
|
+
type: Literal["linear-truss", "elastic-truss", "linear-beam"]
|
|
50
|
+
mat: str = attr(default="material")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MeshDomains(BaseXmlModel, validate_assignment=True):
|
|
54
|
+
solid_domains: list[SolidDomain] = element(default=[], tag="SolidDomain")
|
|
55
|
+
shell_domains: list[ShellDomain] = element(default=[], tag="ShellDomain")
|
|
56
|
+
beam_domains: list[BeamDomain] = element(default=[], tag="BeamDomain")
|
|
57
|
+
|
|
58
|
+
def add_solid_domain(self, new_solid_domain: SolidDomain):
|
|
59
|
+
self.solid_domains.append(new_solid_domain)
|
|
60
|
+
|
|
61
|
+
def add_shell_domain(self, new_shell_domain: ShellDomain):
|
|
62
|
+
self.shell_domains.append(new_shell_domain)
|
|
63
|
+
|
|
64
|
+
def add_beam_domain(self, new_beam_domain: BeamDomain):
|
|
65
|
+
self.beam_domains.append(new_beam_domain)
|