Coverage for cards/loading.py: 90%
96 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 loading cards collection.
4This include loading and constraints, therfore, GRIDS cards
5"""
7import logging
8import re
9from collections import defaultdict
10from pprint import pprint
12import numpy as np
13import pandas as pd
14from numtools.vgextended import loc_array
16from nastranio.cardslib import SimpleCard, SimpleCyclingCard
17from nastranio.decorators import boundary, cached_property, loading_type
20@loading_type()
21class GRAV(SimpleCard):
22 """
23 Acceleration or Gravity Load
24 Defines acceleration vectors for gravity or other acceleration loading.
26 ref: NX Nastran 12 Quick Reference Guide 14-55 (p.1921)
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 """
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}
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.
56 ref: NX Nastran 12 Quick Reference Guide 14-10 (p.1876)
57 """
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}
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.
73 ref: NX Nastran 12 Quick Reference Guide 15-127 (p.2093)
74 """
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}
84@loading_type()
85class LOAD(SimpleCyclingCard):
86 """
87 Static Force
88 Defines a static concentrated force at a grid point by specifying a vector.
90 ref: NX Nastran 12 Quick Reference Guide 14-90 (p.1956)
91 """
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"
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.
109 ref: NX Nastran 12 Quick Reference Guide 16-164 (p.2394)
111 Alternate format (using "THRU") not taken into account.
112 """
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 """
122@boundary
123class SPC1(SimpleCyclingCard):
124 """
125 Single-Point Constraint, Alternate Form
126 Defines a set of single-point constraints.
128 ref: NX Nastran 12 Quick Reference Guide 17-174 (p.2648)
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 """
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 """
152@boundary
153class GRID(SimpleCard):
154 """
155 Grid Point
156 Defines the location of a geometric grid
158 ref: NX Nastran 12 Quick Reference Guide 14-60 (p.1926)
159 """
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"
177 def to_vtk(self, **kwargs):
178 """return grids as VTK legacy format"""
179 from io import StringIO
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
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)
234 if gids:
235 mask = np.isin(_gids, gids, assume_unique=True)
236 _gids = _gids[mask]
237 xyz = xyz[mask]
238 csys = csys[mask]
240 if len(set(csys)) > 1:
241 logging.warning("return coords in different systems")
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
253if __name__ == "__main__":
254 import doctest
256 doctest.testmod(optionflags=doctest.ELLIPSIS)