Coverage for cards/loading.py: 90%

96 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-03-20 20:51 +0100

1""" 

2NASTRAN loading cards collection. 

3 

4This include loading and constraints, therfore, GRIDS cards 

5""" 

6 

7import logging 

8import re 

9from collections import defaultdict 

10from pprint import pprint 

11 

12import numpy as np 

13import pandas as pd 

14from numtools.vgextended import loc_array 

15 

16from nastranio.cardslib import SimpleCard, SimpleCyclingCard 

17from nastranio.decorators import boundary, cached_property, loading_type 

18 

19 

20@loading_type() 

21class GRAV(SimpleCard): 

22 """ 

23 Acceleration or Gravity Load 

24 Defines acceleration vectors for gravity or other acceleration loading. 

25 

26 ref: NX Nastran 12 Quick Reference Guide 14-55 (p.1921) 

27 

28 >>> grav = GRAV() 

29 >>> bulk = ["GRAV 1 0 1. -9. 0. 0.", 

30 ... "GRAV 1 0 1. -9. 0. 0. 5"] 

31 >>> for b in bulk: grav.parse(b) 

32 >>> pprint(grav.export_data()['main']) 

33 {'A': [1.0, 1.0], 

34 'CID': [0, 0], 

35 'MB': [0, 5], 

36 'N1': [-9.0, -9.0], 

37 'N2': [0.0, 0.0], 

38 'N3': [0.0, 0.0], 

39 'SID': [1, 1]} 

40 """ 

41 

42 TABLE = """ 

43| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 

44|------+-----+-----+---+----+----+----+----+---+----| 

45| GRAV | SID | CID | A | N1 | N2 | N3 | MB | | | 

46 """ 

47 DEFAULTS = {"CID": 0, "MB": 0} 

48 

49 

50@loading_type("nodal") 

51class FORCE(SimpleCard): 

52 """ 

53 Static Force 

54 Defines a static concentrated force at a grid point by specifying a vector. 

55 

56 ref: NX Nastran 12 Quick Reference Guide 14-10 (p.1876) 

57 """ 

58 

59 TABLE = """ 

60 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 

61 |-------+-----+---+-----+---+----+----+----+---+----| 

62 | FORCE | SID | G | CID | F | N1 | N2 | N3 | | | 

63 """ 

64 DEFAULTS = {"cid": 0, "N1": 0.0, "N2": 0.0, "N3": 0.0} 

65 

66 

67@loading_type("nodal") 

68class MOMENT(SimpleCard): 

69 """ 

70 Static Moment 

71 Defines a static concentrated moemnt at a grid point by specifying a vector. 

72 

73 ref: NX Nastran 12 Quick Reference Guide 15-127 (p.2093) 

74 """ 

75 

76 TABLE = """ 

77 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 

78 |--------+-----+---+-----+---+----+----+----+---+----| 

79 | MOMENT | SID | G | CID | M | N1 | N2 | N3 | | | 

80 """ 

81 DEFAULTS = {"cid": 0, "N1": 0.0, "N2": 0.0, "N3": 0.0} 

82 

83 

84@loading_type() 

85class LOAD(SimpleCyclingCard): 

86 """ 

87 Static Force 

88 Defines a static concentrated force at a grid point by specifying a vector. 

89 

90 ref: NX Nastran 12 Quick Reference Guide 14-90 (p.1956) 

91 """ 

92 

93 TABLE = """ 

94 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 

95 |------+-----+----+--------+----+----+----+----+----+----| 

96 | LOAD | SID | S | S1 | L1 | S2 | L2 | S3 | L3 | | 

97 | | S4 | L4 | -etc.- | | | | | | | 

98 """ 

99 REPEATED_DATA_NAME = "FACTORS" 

100 

101 

102@loading_type("elemental") 

103class PLOAD4(SimpleCard): 

104 """ 

105 Pressure Load on Shell and Solid Element Faces 

106 Defines a pressure load on a face of a CHEXA, CPENTA, CTETRA, CPYRAM, 

107 CTRIA3, CTRIA6, CTRIAR, CQUAD4, CQUAD8, or CQUADR element. 

108 

109 ref: NX Nastran 12 Quick Reference Guide 16-164 (p.2394) 

110 

111 Alternate format (using "THRU") not taken into account. 

112 """ 

113 

114 TABLE = """ 

115 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 

116 |--------+-----+-----+----+----+----+----+----+--------+----| 

117 | PLOAD4 | SID | EID | P1 | P2 | P3 | P4 | G1 | G3orG4 | | 

118 | | CID | N1 | N2 | N3 | | | | | | 

119 """ 

120 

121 

122@boundary 

123class SPC1(SimpleCyclingCard): 

124 """ 

125 Single-Point Constraint, Alternate Form 

126 Defines a set of single-point constraints. 

127 

128 ref: NX Nastran 12 Quick Reference Guide 17-174 (p.2648) 

129 

130 >>> spc1 = SPC1() 

131 >>> bulk = ["SPC1 290 123 12508", 

132 ... "SPC1 292 123 12", 

133 ... "SPC1 291 123456 12509 2 3 4 5"] 

134 >>> for b in bulk: spc1.parse(b) 

135 >>> pprint(spc1.export_data()['main']) # doctest: +NORMALIZE_WHITESPACE 

136 {'C': [123, 123, 123456], 'SID': [290, 292, 291], 'spc1_gridsetID': [0, 1, 2]} 

137 >>> pprint(spc1.export_data()['spc1_gridset']) # doctest: +NORMALIZE_WHITESPACE 

138 [[{'G': 12508}], 

139 [{'G': 12}], 

140 [{'G': 12509}, {'G': 2}, {'G': 3}, {'G': 4}, {'G': 5}]] 

141 """ 

142 

143 REPEATED_DATA_NAME = "GRIDSET" 

144 TABLE = """ 

145 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 

146 |------+-----+----+----+--------+----+----+----+----+----| 

147 | SPC1 | SID | C | G1 | G2 | G3 | G4 | G5 | G6 | | 

148 | | G7 | G8 | G9 | -etc.- | | | | | | 

149 """ 

150 

151 

152@boundary 

153class GRID(SimpleCard): 

154 """ 

155 Grid Point 

156 Defines the location of a geometric grid 

157 

158 ref: NX Nastran 12 Quick Reference Guide 14-60 (p.1926) 

159 """ 

160 

161 TABLE = """ 

162 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 

163 |------+----+----+----+----+----+----+----+------+----| 

164 | GRID | ID | CP | X1 | X2 | X3 | CD | PS | SEID | | 

165 """ 

166 DEFAULTS = { 

167 "CP": 0, 

168 "CD": 0, 

169 "PS": None, 

170 "SEID": None, 

171 "X1": 0.0, 

172 "X2": 0.0, 

173 "X3": 0.0, 

174 } 

175 XID_FIELDNAME = "ID" 

176 

177 def to_vtk(self, **kwargs): 

178 """return grids as VTK legacy format""" 

179 from io import StringIO 

180 

181 fh = StringIO() 

182 msg = ["POINTS %d double" % len(self)] 

183 coords = self.coords()[1] 

184 np.savetxt(fh, coords, **kwargs) 

185 fh.seek(0) 

186 msg += fh.readlines() 

187 return msg 

188 

189 def coords(self, incsys=None, csysreg=None, gids=None, asdf=False): 

190 """ 

191 return two numpy arrays: one vector of eids and one 3xN matrix of coordinates 

192 """ 

193 cdefined = self.array[["ID", "CP", "X1", "X2", "X3"]] # coordinates as defined 

194 _gids = cdefined["ID"] 

195 xyz = np.vstack((cdefined["X1"], cdefined["X2"], cdefined["X3"])).T 

196 csys = cdefined["CP"] 

197 if gids: 

198 if not isinstance(gids, (list, np.ndarray)): 

199 gids = list(gids) 

200 # ==================================================================== 

201 # calculate coordinates in requested CSYS 

202 # ==================================================================== 

203 if incsys: # not None and not 0 

204 if incsys == -1: # way for _translate_grids_to_0 to bypass 

205 incsys = 0 

206 _initial_gids = _gids.copy() 

207 # which points are not concerned? 

208 nop = csys == incsys 

209 nop_gids, _gids = _gids[nop], _gids[~nop] 

210 nop_xyz, xyz = xyz[nop], xyz[~nop] 

211 nop_csys, csys = csys[nop], csys[~nop] 

212 origins = set(csys) # this will define how many change we will perform 

213 for origin_csys in origins: 

214 csys_obj = csysreg[origin_csys] 

215 # search all the points defined in origin_csys 

216 which = csys == origin_csys 

217 c_gids, c_xyz, c_csys = _gids[which], xyz[which], csys[which] 

218 c_xyz = csys_obj.export_vertices(c_xyz, to_csys=incsys) 

219 c_csys.fill(incsys) 

220 # nothing to change to c_gids 

221 nop_xyz = np.vstack((nop_xyz, c_xyz)) 

222 nop_gids = np.hstack((nop_gids, c_gids)) 

223 nop_csys = np.hstack((nop_csys, c_csys)) 

224 xyz, _gids, csys = nop_xyz, nop_gids, nop_csys 

225 # ---------------------------------------------------------------- 

226 # sort arrays as initially sorted 

227 positions = loc_array(_initial_gids, _gids) 

228 np.put(_gids, positions, _gids) 

229 np.put(xyz[:, 0], positions, xyz[:, 0]) 

230 np.put(xyz[:, 1], positions, xyz[:, 1]) 

231 np.put(xyz[:, 2], positions, xyz[:, 2]) 

232 np.put(csys, positions, csys) 

233 

234 if gids: 

235 mask = np.isin(_gids, gids, assume_unique=True) 

236 _gids = _gids[mask] 

237 xyz = xyz[mask] 

238 csys = csys[mask] 

239 

240 if len(set(csys)) > 1: 

241 logging.warning("return coords in different systems") 

242 

243 if not asdf: 

244 return _gids, xyz, csys 

245 else: 

246 data = np.insert(xyz, 0, csys, axis=1) 

247 df = pd.DataFrame(data, columns=["csys", "X", "Y", "Z"], index=_gids) 

248 df.csys = df.csys.astype(int) 

249 df.index.names = ["gid"] 

250 return df 

251 

252 

253if __name__ == "__main__": 

254 import doctest 

255 

256 doctest.testmod(optionflags=doctest.ELLIPSIS)