Coverage for cards/loading.py: 90%

1"""

2 NASTRAN loading cards collection.

3

4 This include loading and constraints, therfore, GRIDS cards

5"""

6

7 import logging

8 import re

9 from collections import defaultdict

10 from pprint import pprint

11

12 import numpy as np

13 import pandas as pd

14 from numtools.vgextended import loc_array

15

16 from nastranio.cardslib import SimpleCard, SimpleCyclingCard

17 from nastranio.decorators import boundary, cached_property, loading_type

18

19

20@loading_type()

21 class 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")

51 class 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")

68 class 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()

85 class 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")

103 class 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

123 class 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

153 class 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

253 if __name__ == "__main__":

254 import doctest

255

256 doctest.testmod(optionflags=doctest.ELLIPSIS)