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/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)