Coverage for cards/properties.py: 79%
105 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-03-20 20:51 +0100
« prev ^ index » next coverage.py v7.7.0, created at 2025-03-20 20:51 +0100
1"""
2NASTRAN property Cards collection
3"""
4import logging
5import re
6from collections import defaultdict
7from pprint import pprint
9import numpy as np
11from nastranio.cardslib import (
12 ComplexCard,
13 RepeatedRowsCard,
14 SimpleCard,
15 SimpleCyclingCard,
16)
17from nastranio.decorators import cached_property, fem_property
20@fem_property
21class PCOMP(SimpleCyclingCard):
22 """
23 Layered Composite Element Property.
24 Defines the properties of an n-ply composite material laminate.
26 ref: NX Nastran 12 Quick Reference Guide 16-97 (p.2327)
29 >>> pcomp = PCOMP()
30 >>> pcomp.append_fields_list(
31 ... [ 1001, None, 0.00361, None, None, None, None, None, '+',
32 ... '+', 1002, 0.018, 0.0, 'YES', 1003, 0.339, 0.0, 'YES', '+',
33 ... '+', 1009, 0.018, 5.0, 'YES'])
34 >>> pcomp.append_fields_list(
35 ... [ 1002, None, 0.005, None, None, None, None, None, '+',
36 ... '+', 1002, 0.018, 0.0, 'YES', 1003, 0.339, None, 'YES', '+',
37 ... '+', 1009, 0.018, 5.0, 'YES'])
38 >>> # check main data:
39 >>> pprint(pcomp.carddata['main']) # doctest: +NORMALIZE_WHITESPACE
40 defaultdict(<class 'list'>,
41 {'FT': [None, None],
42 'GE': [None, None],
43 'LAM': [None, None],
44 'NSM': [0.00361, 0.005],
45 'PID': [1001, 1002],
46 'SB': [None, None],
47 'TREF': [None, None],
48 'Z0': [None, None],
49 'pcomp_layupID': [0, 0]})
50 >>> # check LAYUPs creation. Both cards come with the same layout values. Therefore
51 >>> # only one LAYUP (made of three plies) is created:
52 >>> pprint(pcomp.carddata['pcomp_layup']) # doctest: +NORMALIZE_WHITESPACE
53 [[{'MID': 1002, 'SOUT': 'YES', 'T': 0.018, 'THETA': 0.0},
54 {'MID': 1003, 'SOUT': 'YES', 'T': 0.339, 'THETA': 0.0},
55 {'MID': 1009, 'SOUT': 'YES', 'T': 0.018, 'THETA': 5.0}]]
56 """
58 DIM = "2d"
59 REPEATED_DATA_NAME = "layup"
60 TABLE = """
61| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
62|-------+------+----+--------+-------+--------+------+--------+-------+----|
63| PCOMP | PID | Z0 | NSM | SB | FT | TREF | GE | LAM | |
64| "" | MID1 | T1 | THETA1 | SOUT1 | MID2 | T2 | THETA2 | SOUT2 | |
65| "" | MID3 | T3 | THETA3 | SOUT3 | -etc.- | | | | |
66"""
67 DEFAULTS = {
68 "NSM": 0.0,
69 "Z0": None,
70 "SB": None,
71 "FT": None,
72 "TREF": None,
73 "GE": None,
74 "LAM": None,
75 }
76 REPEATED_DEFAULTS = {"THETA": 0.0, "SOUT": "NO"}
77 COMMENTS_KEY = "Property"
79 @cached_property
80 def thk(self):
81 """return a dict of thicknesses"""
82 # calculate thicknesses for all layups
83 layup_thks = {}
84 for layID, data in enumerate(self.carddata["pcomp_layup"]):
85 ts = [ply["T"] for ply in data]
86 layup_thks[layID] = sum(ts)
87 # map to PIDS
88 thks = {
89 pid: layup_thks[lay]
90 for pid, lay in zip(
91 self.carddata["main"]["PID"], self.carddata["main"]["pcomp_layupID"]
92 )
93 }
94 return {
95 "data": np.array(list(thks.values())),
96 "index": np.array(list(thks.keys())),
97 "name": "pid2thk",
98 }
100 @cached_property
101 def pid2mids(self):
102 """This ovverides the default "pid2mids" defined in `cardslib` module"""
103 ret = {}
105 for ix, pid in enumerate(self.carddata["main"]["PID"]):
106 ret[pid] = set()
107 layup_id = self.carddata["main"]["pcomp_layupID"][ix]
108 layups = self.carddata["pcomp_layup"][layup_id]
109 for layup in layups:
110 ret[pid].add(layup["MID"])
111 ret[pid] = frozenset(ret[pid])
112 return ret
115@fem_property
116class PBAR(SimpleCard):
117 """
118 Simple Beam Property.
119 Defines the properties of a simple beam element (CBAR entry).
121 ref: NX Nastran 12 Quick Reference Guide 16-32 (p.2262)
122 """
124 DIM = "1d"
125 COMMENTS_KEY = "Property"
126 TABLE = """
127| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
128|------+-----+-----+-----+----+----+----+-----+----+----|
129| PBAR | PID | MID | A | I1 | I2 | J | NSM | |
130| "" | C1 | C2 | D1 | D2 | E1 | E2 | F1 | F2 |
131| "" | K1 | K2 | I12 | | | | | |
132"""
134 DEFAULTS = {
135 "K1": 0.0,
136 "K2": 0.0,
137 "A": 0.0,
138 "I1": 0.0,
139 "I2": 0.0,
140 "I12": 0.0,
141 "J": 0.0,
142 "NSM": 0.0,
143 "C1": 0.0,
144 "D1": 0.0,
145 "E1": 0.0,
146 "F1": 0.0,
147 "C2": 0.0,
148 "D2": 0.0,
149 "E2": 0.0,
150 "F2": 0.0,
151 }
154@fem_property
155class PSHEAR(SimpleCard):
156 """
157 SHEAR Panel Property
158 Defines the propertires of a shear panel (CSHEAR entry).
160 ref: NX Nastran 12 Quick Reference Guide 16-208 (p.2438)
163 """
165 DIM = "2d"
166 COMMENTS_KEY = "Property"
167 TABLE = """
168| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
169|--------+-----+------+------+------+----+-----+----+----+----|
170| PSHEAR | PID | MID | T | NSM | F1 | F2 | | | |
171"""
172 DEFAULTS = {
173 "NSM": 0.0,
174 "F1": 0.0,
175 "F2": 0.0,
176 }
178 @cached_property
179 def thk(self):
180 """return a dict of thicknesses"""
181 thks = {
182 pid: thk
183 for pid, thk in zip(
184 self.carddata["main"]["PID"], self.carddata["main"]["T"]
185 )
186 }
187 # thks -> {1: 0.09, 203: 1.9}
188 return {
189 "data": np.array(list(thks.values())),
190 "index": np.array(list(thks.keys())),
191 "name": "pid2thk",
192 }
195@fem_property
196class PSHELL(SimpleCard):
197 """
198 Shell Element Property
199 Defines the membrane, bending, transverse shear, and coupling properties of thin
200 shell elements.
202 ref: NX Nastran 12 Quick Reference Guide 16-210 (p.2440)
203 """
205 DIM = "2d"
206 COMMENTS_KEY = "Property"
207 TABLE = """
208| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
209|--------+-----+------+------+------+----------+------+------+-----+----|
210| PSHELL | PID | MID1 | T | MID2 | 12I/T**3 | MID3 | TS/T | NSM | |
211| | Z1 | Z2 | MID4 | | | | | | |
212"""
213 DEFAULTS = {
214 "12I/T**3": None,
215 "TS/T": None,
216 "NSM": 0.0,
217 "Z1": None,
218 "Z2": None,
219 "MID4": None,
220 }
222 @cached_property
223 def thk(self):
224 """return a dict of thicknesses"""
225 thks = {
226 pid: thk
227 for pid, thk in zip(
228 self.carddata["main"]["PID"], self.carddata["main"]["T"]
229 )
230 }
231 # thks -> {1: 0.09, 203: 1.9}
232 return {
233 "data": np.array(list(thks.values())),
234 "index": np.array(list(thks.keys())),
235 "name": "pid2thk",
236 }
239@fem_property
240class PROD(SimpleCard):
241 """
242 Rod Property.
244 Defines the properties of a rod element (CROD entry).
246 ref: NX Nastran 12 Quick Reference Guide 16-206 (p.2436)
247 """
249 DIM = "1d"
250 COMMENTS_KEY = "Property"
251 TABLE = """
252 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
253 |------+-----+-----+---+---+---+-----+---+---+----|
254 | PROD | PID | MID | A | J | C | NSM | | | |
255 """
257 DEFAULTS = {"C": 0, "NSM": 0}
260@fem_property
261class PBUSH(ComplexCard):
262 """
263 Generalized Spring-and-Damper Property
264 Defines the nominal property values for a generalized spring-and-damper structural
265 element.
267 ref: NX Nastran 12 Quick Reference Guide 16-83 (p.2800)
268 """
270 DIM = "1d"
271 COMMENTS_KEY = "Property"
272 TABLE = """
273 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
274 |-------+-----+-------+-----+-----+-----+-----+-----+-----+----|
275 | PBUSH | PID | "K" | K1 | K2 | K3 | K4 | K5 | K6 | |
276 | | | "B" | B1 | B2 | B3 | B4 | B5 | B6 | |
277 | | | "GE" | GE1 | GE2 | GE3 | GE4 | GE5 | GE6 | |
278 | | | "RCV" | SA | ST | EA | ET | | | |
279 """
281 DEFAULTS = {
282 "C": 0,
283 "NSM": 0,
284 '"K"': "K",
285 "K1": None,
286 "K2": None,
287 "K3": None,
288 "K4": None,
289 "K5": None,
290 "K6": None,
291 '"B"': "B",
292 "B1": None,
293 "B2": None,
294 "B3": None,
295 "B4": None,
296 "B5": None,
297 "B6": None,
298 '"GE"': "GE",
299 "GE1": None,
300 "GE2": None,
301 "GE3": None,
302 "GE4": None,
303 "GE5": None,
304 "GE6": None,
305 '"RCV"': "RCV",
306 "SA": None,
307 "ST": None,
308 "EA": None,
309 "ET": None,
310 }
312 def clean_sparams(self, sparams):
313 """remove"""
314 Kfields = set((f"K{i}" for i in range(1, 6)))
315 Ks = set((sparams[Ki] for Ki in Kfields))
316 if Ks == {None}:
317 Kfields.add('"K"')
318 for field in Kfields:
319 sparams.pop(field)
320 Bfields = set((f"B{i}" for i in range(1, 6)))
321 Bs = set((sparams[Bi] for Bi in Bfields))
322 if Bs == {None}:
323 Bfields.add('"B"')
324 for field in Bfields:
325 sparams.pop(field)
326 GEfields = set((f"GE{i}" for i in range(1, 6)))
327 GEs = set((sparams[GEi] for GEi in GEfields))
328 if GEs == {None}:
329 GEfields.add('"GE"')
330 for field in GEfields:
331 sparams.pop(field)
334@fem_property
335class PBEAM(RepeatedRowsCard):
336 """
337 Beam Property
338 Defines the properties of a beam element (CBEAM entry). This element may be
339 used to model tapered beams.
341 ref: NX Nastran 12 Quick Reference Guide 16-48 (p.2278)
343 > If SO is “YESA” or “NO”, the third continuation entry, which contains the fields
344 > C1 through F2, must be omitted. If SO is “YES”, the continuation for Ci, Di, Ei,
345 > and Fi must be the next entry.
347 > ...
349 > The fourth and fifth continuation entries, which contain fields K1 through
350 > N2(B), are optional and may be omitted if the default values are appropriate.
351 """
353 DIM = "1d"
354 COMMENTS_KEY = "Property"
355 TABLE = """
356 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
357 |-------+-------+--------+-------+-------+-------+--------+-------+--------+----|
358 | PBEAM | PID | MID | A(A) | I1(A) | I2(A) | I12(A) | J(A) | NSM(A) | |
359 | | C1(A) | C2(A) | D1(A) | D2(A) | E1(A) | E2(A) | F1(A) | F2(A) | |
360 """
362 REPEATED_ROWS_TABLE = """
363 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
364 |---+----+------+----+----+----+-----+----+-----+----|
365 | | SO | X/XB | A | I1 | I2 | I12 | J | NSM | |
366 | | C1 | C2 | D1 | D2 | E1 | E2 | F1 | F2 | |
367 """
369 TRAILING_ROWS_TABLE = """
370 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
371 |---+-------+-------+-------+-------+--------+--------+-------+-------+----|
372 | | K1 | K2 | S1 | S2 | NSI(A) | NSI(B) | CW(A) | CW(B) | |
373 | | M1(A) | M2(A) | M1(B) | M2(B) | N1(A) | N2(A) | N1(B) | N2(B) |
374 """
376 REPEATED_ROWS_NAME = "stations"
377 TRIGGER_REPEATED_ON = str
378 SKIP_NEXT_ROW_ON = ("SO", ("YESA", "NO"))
379 DEFAULTS = {"I12(A)": 0.0, "J(A)": 0.0, "NSM(A)": 0.0}
382if __name__ == "__main__":
383 import doctest
385 doctest.testmod(optionflags=doctest.ELLIPSIS)