Coverage for cards/properties.py: 79%

105 statements  

« 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 

8 

9import numpy as np 

10 

11from nastranio.cardslib import ( 

12 ComplexCard, 

13 RepeatedRowsCard, 

14 SimpleCard, 

15 SimpleCyclingCard, 

16) 

17from nastranio.decorators import cached_property, fem_property 

18 

19 

20@fem_property 

21class PCOMP(SimpleCyclingCard): 

22 """ 

23 Layered Composite Element Property. 

24 Defines the properties of an n-ply composite material laminate. 

25 

26 ref: NX Nastran 12 Quick Reference Guide 16-97 (p.2327) 

27 

28 

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 """ 

57 

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" 

78 

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 } 

99 

100 @cached_property 

101 def pid2mids(self): 

102 """This ovverides the default "pid2mids" defined in `cardslib` module""" 

103 ret = {} 

104 

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 

113 

114 

115@fem_property 

116class PBAR(SimpleCard): 

117 """ 

118 Simple Beam Property. 

119 Defines the properties of a simple beam element (CBAR entry). 

120 

121 ref: NX Nastran 12 Quick Reference Guide 16-32 (p.2262) 

122 """ 

123 

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""" 

133 

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 } 

152 

153 

154@fem_property 

155class PSHEAR(SimpleCard): 

156 """ 

157 SHEAR Panel Property 

158 Defines the propertires of a shear panel (CSHEAR entry). 

159 

160 ref: NX Nastran 12 Quick Reference Guide 16-208 (p.2438) 

161 

162 

163 """ 

164 

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 } 

177 

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 } 

193 

194 

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. 

201 

202 ref: NX Nastran 12 Quick Reference Guide 16-210 (p.2440) 

203 """ 

204 

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 } 

221 

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 } 

237 

238 

239@fem_property 

240class PROD(SimpleCard): 

241 """ 

242 Rod Property. 

243 

244 Defines the properties of a rod element (CROD entry). 

245 

246 ref: NX Nastran 12 Quick Reference Guide 16-206 (p.2436) 

247 """ 

248 

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 """ 

256 

257 DEFAULTS = {"C": 0, "NSM": 0} 

258 

259 

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. 

266 

267 ref: NX Nastran 12 Quick Reference Guide 16-83 (p.2800) 

268 """ 

269 

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 """ 

280 

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 } 

311 

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) 

332 

333 

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. 

340 

341 ref: NX Nastran 12 Quick Reference Guide 16-48 (p.2278) 

342 

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. 

346 

347 > ... 

348 

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 """ 

352 

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 """ 

361 

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 """ 

368 

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 """ 

375 

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} 

380 

381 

382if __name__ == "__main__": 

383 import doctest 

384 

385 doctest.testmod(optionflags=doctest.ELLIPSIS)