Coverage for mesh_api/mod.py: 11%

158 statements  

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

1"""Mesh Modification package""" 

2 

3import logging 

4import numbers 

5from collections import defaultdict 

6 

7import numpy as np 

8from numpy.lib import recfunctions as rfn 

9 

10import nastranio.cards as cards 

11from nastranio.mesh_api.rtree_indexes import RTrees 

12from nastranio.utils import project_point, project_point_fast 

13 

14 

15class Mod: 

16 def set_registry(self, registry, index_elems=("0d", "1d", "2d", "3d")): 

17 self.reg = registry 

18 self.bulk = registry.container["bulk"] 

19 self.mesh = registry.mesh 

20 self.rtrees = RTrees(registry, index_elems=index_elems) 

21 self.rtrees.build() 

22 

23 def node_move_to(self, gid, coords): 

24 """move node `gid` to new coordinates""" 

25 logging.info(f"move gid{gid} to {coords}") 

26 griddata, rowno = self.bulk["GRID"].query_id(gid, with_loc=True) 

27 rowno = rowno[0][0] 

28 grid_coords = np.array(coords) 

29 old_grid_coords = rfn.structured_to_unstructured(griddata[["X1", "X2", "X3"]]) 

30 old_grid_coords = old_grid_coords[0] 

31 # collect current (pre-mod) impacted elements bboxes 

32 impacted_eids = self.reg.mesh.gid2eids(gids=(gid,))[gid] 

33 premod_elts_bboxes = { 

34 eid: self.reg.mesh.get_eid_bbox(eid) for eid in impacted_eids 

35 } 

36 logging.debug(f"move gid#{gid} from {old_grid_coords}") 

37 logging.debug(f" to {grid_coords=}") 

38 logging.debug(f" {rowno=}") 

39 logging.debug(f" impacted elts bboxes={premod_elts_bboxes.keys()}") 

40 # ===================================================================== 

41 # update mesh 

42 # ===================================================================== 

43 # change GRID carddata 

44 self.bulk["GRID"].carddata["main"]["X1"][rowno] = grid_coords[0] 

45 self.bulk["GRID"].carddata["main"]["X2"][rowno] = grid_coords[1] 

46 self.bulk["GRID"].carddata["main"]["X3"][rowno] = grid_coords[2] 

47 # --------------------------------------------------------------------- 

48 # rebuild caches 

49 self.bulk["GRID"].clear_caches() 

50 # ===================================================================== 

51 # update rtrees 

52 # ===================================================================== 

53 self.rtrees.update_grid(gid, old_coords=old_grid_coords, coords=grid_coords) 

54 for eid, old_coords in premod_elts_bboxes.items(): 

55 coords = self.reg.mesh.get_eid_bbox(eid) 

56 logging.info(f"moved {gid} from {old_coords} to {coords}") 

57 self.rtrees.update_element(eid, old_coords=old_coords, coords=coords) 

58 

59 def node_move_by(self, gid, vector): 

60 """move node `gid` by vector""" 

61 logging.info("move gid{gid} by vector {coords}") 

62 griddata, rowno = self.bulk["GRID"].query_id(gid, with_loc=True) 

63 vector = np.array(vector) 

64 old_coords = rfn.structured_to_unstructured(griddata[["X1", "X2", "X3"]])[0] 

65 coords = old_coords + vector 

66 logging.debug(f"move gid#{gid} by vector={vector}") 

67 logging.debug(f" from {old_coords=} to {coords=}") 

68 return self.node_move_to(gid, coords=coords) 

69 

70 def node_add(self, coords, gid=None, clear_caches=False): 

71 """add grid <gid> at provided coords""" 

72 if not gid: 

73 gid = self.mesh.next_unused_gid() 

74 else: 

75 # check that node gid does not exist 

76 if gid in self.mesh.gid2eids(): 

77 raise ValueError(f"node {gid} already exists") 

78 # --------------------------------------------------------------------- 

79 # update registry 

80 self.reg.container["bulk"]["GRID"].append_sparams( 

81 {"ID": gid, "X1": coords[0], "X2": coords[1], "X3": coords[2]} 

82 ) 

83 # --------------------------------------------------------------------- 

84 # update rtree 

85 self.rtrees.add_grid(gid, coords=coords) 

86 # --------------------------------------------------------------------- 

87 # clear caches 

88 if clear_caches: 

89 self.bulk["GRID"].clear_caches() 

90 return gid 

91 

92 def force_define_subcase(self, lcid, spcid=1, title=None): 

93 if not title: 

94 title = f"LCID#{lcid} SPC#{spcid}" 

95 lcid_key = f"SUBCASE {lcid}" 

96 cases = self.reg.container["cases"] 

97 if lcid_key not in cases: 

98 cases[lcid_key] = { 

99 "SUBTITLE": title, 

100 "SPC": spcid, 

101 "LOAD": lcid, 

102 } 

103 

104 def force_add( 

105 self, 

106 lcid, 

107 gid, 

108 scale=1.0, 

109 fx=0.0, 

110 fy=0.0, 

111 fz=0.0, 

112 mx=0.0, 

113 my=0.0, 

114 mz=0.0, 

115 lcid_title=None, 

116 clear_caches=False, 

117 ): 

118 for f in ("FORCE", "MOMENT"): 

119 if f not in self.reg.container["bulk"]: 

120 self.reg.container["bulk"][f] = getattr(cards, f)() 

121 # --------------------------------------------------------------------- 

122 # update registry 

123 self.reg.container["bulk"]["FORCE"].append_sparams( 

124 {"SID": lcid, "G": gid, "CID": 0, "F": scale, "N1": fx, "N2": fy, "N3": fz} 

125 ) 

126 self.reg.container["bulk"]["MOMENT"].append_sparams( 

127 {"SID": lcid, "G": gid, "CID": 0, "M": scale, "N1": mx, "N2": my, "N3": mz} 

128 ) 

129 

130 if clear_caches: 

131 self.bulk["FORCE"].clear_caches() 

132 self.bulk["MOMENT"].clear_caches() 

133 

134 def force_clear_caches(self): 

135 self.bulk["FORCE"].clear_caches() 

136 self.bulk["MOMENT"].clear_caches() 

137 

138 def elem1d_split( 

139 self, 

140 eid, 

141 where, 

142 rtree=None, 

143 new_gid=None, 

144 replicate_pins=False, 

145 clear_caches=False, 

146 ): 

147 """ 

148 split 1d element: 

149 * if where is a 0<where<1 number, use it as relative length 

150 * if `where` is a list of numbers, assume it as new location 

151 

152 If `replicate_pins` is `False` (default`), then: 

153 * existing element PB is set to None 

154 * new element PA is set to None 

155 * new element PB is set to initial element PB 

156 

157 If `replicate_pins` is `True` (default is `False`), then new 

158 new element has the same PIN FLAGS `"PA"` and `"PB"` than 

159 initial element 

160 

161 

162 """ 

163 cardname = self.reg.mesh.eid2card(skipcache=True)[eid] 

164 card = self.reg.container["bulk"][cardname] 

165 if isinstance(where, numbers.Number): 

166 if not 0 < where < 1: 

167 raise ValueError(f"`where` ({where}) must be in ]0,1[ range.") 

168 # --------------------------------------------------------------------- 

169 # find location of split 

170 _gid_a, _gid_b = self.reg.mesh.eid2gids(eids=(eid,), keep_order=True)[eid] 

171 _length = self.reg.mesh.length(eids=(eid,))["data"][0] 

172 _offset = self.reg.mesh.normals().loc[eid] * where * _length 

173 _base_point = ( 

174 self.bulk["GRID"].coords(asdf=True)[["X", "Y", "Z"]].loc[_gid_a] 

175 ) 

176 new_gid_coords = (_base_point + _offset).to_list() 

177 else: 

178 # assume a list/tuple of numbers is passed 

179 new_gid_coords = where 

180 initial_eid_bbox = self.reg.mesh.get_eid_bbox(eid) 

181 # --------------------------------------------------------------------- 

182 # create gid 

183 new_gid = self.node_add(gid=new_gid, coords=new_gid_coords, clear_caches=False) 

184 self.rtrees.add_grid(new_gid, coords=new_gid_coords) 

185 # --------------------------------------------------------------------- 

186 # change final node of existing element 

187 # copy property from initial element 

188 eid_ix, old_gid = card.update_gid(eid=eid, gidno=1, new_gid=new_gid) 

189 old_properties = {k: v[eid_ix] for k, v in card.carddata["main"].items()} 

190 new_properties = old_properties.copy() 

191 # --------------------------------------------------------------------- 

192 # create new element 

193 new_eid = self.mesh.next_unused_eid() 

194 gids_header = card.gids_header 

195 new_properties[card.XID_FIELDNAME] = new_eid 

196 new_properties[gids_header[0]] = new_gid 

197 new_properties[gids_header[1]] = old_gid 

198 # --------------------------------------------------------------------- 

199 # if `not replicate_pins` only keep defined pins 

200 if not replicate_pins: 

201 pina, pinb = old_properties["PA"], old_properties["PB"] 

202 if pina or pinb: 

203 card.carddata["main"]["PB"][eid_ix] = None 

204 new_properties["PA"] = None 

205 new_properties["PB"] = pinb 

206 self.reg.container["bulk"][card.name].append_sparams(new_properties) 

207 # --------------------------------------------------------------------- 

208 # update rtree 

209 bbox = self.reg.mesh.get_eid_bbox(eid=new_eid) 

210 self.rtrees.add_elem(cardname, new_eid, coords=bbox, rtree=rtree) 

211 self.rtrees.update_element( 

212 eid, 

213 old_coords=initial_eid_bbox, 

214 coords=self.reg.mesh.get_eid_bbox(eid=eid), 

215 rtree=rtree, 

216 ) 

217 # --------------------------------------------------------------------- 

218 # clear caches 

219 if clear_caches: 

220 self.bulk["GRID"].clear_caches() 

221 card.clear_caches() 

222 self.reg.mesh.clear_caches() 

223 return card, new_eid, new_gid 

224 

225 def multiple_elem1d_split( 

226 self, 

227 points, 

228 cardname=None, 

229 rtree=None, 

230 eids=None, 

231 margin=0.01, 

232 round_t_digits=None, 

233 ): 

234 """split elements found close to each point""" 

235 if eids: 

236 rtree = self.reg.mesh.mod.rtrees.create_rtree(eids=eids) 

237 if not round_t_digits: 

238 # round `t` to number of digits of margin, therefore, defaulted to 2 

239 round_t_digits = len(str(float(margin)).split(".")[-1]) 

240 _details = [] 

241 _historic_by_eid = defaultdict(list) 

242 _prev_eids = defaultdict(set) 

243 for i, point in enumerate(points): 

244 prev_eids = self.rtrees.nearest_elements( 

245 point, cardname=cardname, rtree=rtree, astype=list 

246 ) 

247 prev_eid = next(iter(prev_eids)) 

248 if cardname: 

249 cardnames = (cardname,) 

250 geoms = self.mesh.geom_1d_elements(cardnames=cardnames) 

251 elt = geoms.loc[prev_eid] 

252 ga = elt[["XA", "YA", "ZA"]].values 

253 gb = elt[["XB", "YB", "ZB"]].values 

254 proj, t = project_point_fast(point, ga, gb) 

255 # ----------------------------------------------------------------- 

256 # if t == 0 or == 1, len(prev_eids) should be >1 

257 if margin < t < (1 - margin): 

258 point = proj 

259 card, new_eid, new_gid = self.elem1d_split( 

260 eid=prev_eid, where=point, clear_caches=False, rtree=rtree 

261 ) 

262 else: 

263 if t < margin: 

264 new_gid = int(elt["GA"]) 

265 point = elt[["XA", "YA", "ZA"]].tolist() 

266 t = 0 

267 else: 

268 new_gid = int(elt["GB"]) 

269 point = elt[["XB", "YB", "ZB"]].tolist() 

270 t = 1 

271 new_eid = None 

272 # ----------------------------------------------------------------- 

273 # keep track of initial element 

274 if new_eid: 

275 # if a new element has been created, append it to the list of 

276 # old element children 

277 for root_eid, _eids_ in _prev_eids.items(): 

278 if prev_eid in _eids_: 

279 break 

280 else: 

281 # nothing found. It's an element that hasn't been already split 

282 root_eid = prev_eid 

283 _prev_eids[root_eid].add(new_eid) 

284 else: 

285 # no element split. An existing node is returned, so, let's return the 

286 # existing element as mother and child element 

287 root_eid = prev_eid 

288 new_eid = prev_eid 

289 # but maybe prev_eid comes from a previous plit...? 

290 _historic_by_eid[new_eid].append(root_eid) 

291 __details = { 

292 "prev_eids": _historic_by_eid[new_eid], # ths whole historic 

293 "prev_eid": root_eid, # really the last one 

294 "eid": new_eid, 

295 "gid": new_gid, 

296 "XA": point[0], 

297 "XB": point[1], 

298 "XC": point[2], 

299 "t": round(t, round_t_digits), 

300 } 

301 _details.append(__details) 

302 # ----------------------------------------------------------------- 

303 # rebuild caches 

304 self.bulk["GRID"].clear_caches() 

305 card = self.reg.container["bulk"][cardname] 

306 card.clear_caches() 

307 self.reg.mesh.clear_caches() 

308 return _details