Coverage for fiqus/geom_generators/GeometryCCT.py: 96%

653 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2024-05-20 03:24 +0200

1import os 

2import copy 

3import math 

4import collections 

5import logging 

6 

7import json 

8import timeit 

9import numpy as np 

10import gmsh 

11 

12from fiqus.data import RegionsModelFiQuS as Reg_Mod_FiQ 

13from fiqus.data.DataWindingsCCT import WindingsInformation # for winding information 

14from fiqus.utils.Utils import FilesAndFolders as uff 

15from fiqus.utils.Utils import GmshUtils 

16 

17logger = logging.getLogger(__name__) 

18class Hexahedrons: 

19 

20 def __init__(self): 

21 """ 

22 Points generator for CCT windings to give hexahedron volumes for hexahedron meshing in Gmsh. 

23 """ 

24 self.vertices_to_surf = [[0, 4, 7, 3], [1, 5, 6, 2], [1, 5, 4, 0], [2, 6, 7, 3], [0, 1, 2, 3], [4, 5, 6, 7]] 

25 # Node ordering following Gmsh for hexahedron https://gmsh.info/doc/texinfo/gmsh.html#Legacy-formats --> node ordering 

26 # above _vertices_to_surf is only used for manipulation of the hexahedra points on the data model level. Gmsh uses points connections and line connections for creating surfaces 

27 

28 self.hexes = {} 

29 self._corner_points = {} 

30 self._sur_names = ['x-', 'x+', 'y-', 'y+', 'z-', 'z+'] 

31 self._op_sign = {'+': '-', '-': '+'} 

32 self._sign = {1: '+', -1: '-'} 

33 

34 def _add_corner_points(self, corner_num, x, y, z, ct): 

35 self._corner_points[corner_num] = {'x': x, 'y': y, 'z': z, 'ct': ct} # ct is channel turn 

36 

37 def _generate_hexes(self): 

38 for h in range(len(self._corner_points[0]['x'])): 

39 self.hexes[h] = {} 

40 for p_num, p_coor in self._corner_points.items(): 

41 self.hexes[h][p_num] = {} 

42 for coor in ['x', 'y', 'z']: 

43 self.hexes[h][p_num][coor] = p_coor[coor][h] 

44 self.hexes[h]['ct'] = p_coor['ct'][h] 

45 return p_coor['ct'][h] 

46 

47 def _add_elem_to_elem(self, start_elem, dir_str, dist): 

48 """ 

49 :param start_elem: idx of start element 

50 :param dir_str: direction of adding element, options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+' 

51 :param dist: length of new element in the direction specified above [m] 

52 :return: index of created element 

53 """ 

54 op_dir = dir_str[0] + self._op_sign[dir_str[1]] 

55 new_elem = start_elem + math.copysign(1, start_elem) 

56 self.hexes[new_elem] = {} 

57 source_points = self.vertices_to_surf[self._sur_names.index(dir_str)] 

58 destination_points = self.vertices_to_surf[self._sur_names.index(op_dir)] 

59 for d_p, s_p in zip(destination_points + source_points, source_points + source_points): 

60 self.hexes[new_elem][d_p] = copy.deepcopy(self.hexes[start_elem][s_p]) 

61 for s_p in source_points: # modif_points: 

62 self.hexes[new_elem][s_p][dir_str[0]] = self.hexes[new_elem][s_p][dir_str[0]] + dist 

63 self.hexes[new_elem]['ct'] = self.hexes[start_elem]['ct'] 

64 return new_elem 

65 

66 def _reorder_and_renumber_hexes(self): 

67 self.hexes = collections.OrderedDict(sorted(self.hexes.items())) 

68 first_hex_num = list(self.hexes.keys())[0] 

69 temp_dict = {} 

70 for h_num, h_dict in self.hexes.items(): 

71 temp_dict[h_num - first_hex_num + 1] = h_dict 

72 self.hexes = temp_dict 

73 for h_num, h_dict in self.hexes.items(): 

74 turn = h_dict.pop('ct', None) 

75 self.hexes[h_num] = collections.OrderedDict(sorted(h_dict.items())) 

76 self.hexes[h_num]['ct'] = turn 

77 

78 

79class Winding(Hexahedrons): 

80 

81 def __init__(self, cctdm, layer, post_proc=False): 

82 

83 super(Winding, self).__init__() 

84 self.cctdm = cctdm 

85 self.layer = layer 

86 self.post_proc = post_proc 

87 self.z_corr = 0 

88 self._populate_corners() 

89 self.z_corr = self._center_z_and_offset() 

90 self._generate_hexes() 

91 

92 def _populate_corners(self): 

93 r""" 

94 Generates hexahedron with 8 corners numbered 0-7. The size of hexahedron is taken from groove size of cct as specified in yaml. The length is from number of elements per turn. 

95 y (v) 

96 3----------2 

97 |\ ^ |\ 

98 | \ | | \ 

99 | \ | | \ 

100 | 7------+---6 

101 | | +-- |-- | -> x (u) 

102 0---+---\--1 | 

103 \ | \ \ | 

104 \ | \ \ | 

105 \| z(w)\| 

106 4----------5 

107 

108 :return: none 

109 """ 

110 self._sign_of_rot = math.copysign(1, self.cctdm.geometry.windings.alphas[self.layer]) 

111 if self._sign_of_rot > 0: 

112 corner_seq = { 

113 0: {'w_s': -1, 'h_s': -1, 'far': False}, # 'blcf' 

114 1: {'w_s': -1, 'h_s': 1, 'far': False}, # 'brcf' 

115 2: {'w_s': 1, 'h_s': 1, 'far': False}, # 'trcf' 

116 3: {'w_s': 1, 'h_s': -1, 'far': False}, # 'tlcf' 

117 4: {'w_s': -1, 'h_s': -1, 'far': True}, # bottom left corner close #'blcc' 

118 5: {'w_s': -1, 'h_s': 1, 'far': True}, # bottom right corner close #'brcc' 

119 6: {'w_s': 1, 'h_s': 1, 'far': True}, # top right corner close #'trcc' 

120 7: {'w_s': 1, 'h_s': -1, 'far': True}, # top right corner close #'tlcc' 

121 } 

122 else: 

123 corner_seq = { 

124 0: {'w_s': 1, 'h_s': 1, 'far': False}, # 'trcf' 

125 1: {'w_s': 1, 'h_s': -1, 'far': False}, # 'brcf' 

126 2: {'w_s': -1, 'h_s': -1, 'far': False}, # 'blcf' 

127 3: {'w_s': -1, 'h_s': 1, 'far': False}, # 'tlcf' 

128 4: {'w_s': 1, 'h_s': 1, 'far': True}, # top right corner close #'trcc' 

129 5: {'w_s': 1, 'h_s': -1, 'far': True}, # bottom right corner close #'brcc' 

130 6: {'w_s': -1, 'h_s': -1, 'far': True}, # bottom left corner close #'blcc' 

131 7: {'w_s': -1, 'h_s': 1, 'far': True}, # top left corner close #'tlcc' 

132 } 

133 for corner_num, corner_dict in corner_seq.items(): 

134 self._add_corner_points(corner_num, *self.calc_turns_corner_coords(corner_dict['w_s'], corner_dict['h_s'], far=corner_dict['far'])) 

135 

136 def calc_turns_corner_coords(self, w_s=1, h_s=1, far=False): 

137 """ 

138 Based on https://doi.org/10.1016/j.cryogenics.2020.103041 

139 :param h_s: wire height sign 

140 :param w_s: wire width sign 

141 :param far: for far surface set to True, for close surface set to False. This refers to walls of hexagon. 

142 :return: 

143 """ 

144 r_wm = self.cctdm.geometry.windings.r_wms[self.layer] # radius of winding layer, in the middle of the conductor groove [m] 

145 w_i = self.cctdm.geometry.windings.lps[self.layer] # layer pitch [m] 

146 n_turns = self.cctdm.geometry.windings.n_turnss[self.layer] # number of turns [-] 

147 nept = self.cctdm.geometry.windings.ndpts[self.layer] - 1 # number of elements per turn [-] 

148 alpha = self.cctdm.geometry.windings.alphas[self.layer] * np.pi / 180 # inclination of the winding [deg] 

149 theta_0 = 0 # offset start at theta_0, i.e. angular position of the beginning of the 1st turn[deg] 

150 z_0 = 0 # offset start at z_0, i.e. axial position of the beginning of the 1st turn [m] 

151 hh = 0.5 * h_s * self.cctdm.geometry.windings.wwhs[self.layer] # half of h (groove height) 

152 hw = 0.5 * w_s * self.cctdm.geometry.windings.wwws[self.layer] # half of w (groove width) 

153 tot_angle = 2 * np.pi * (n_turns + 1 / nept) 

154 tot_points = int(nept * n_turns) + 1 

155 if alpha > 0: 

156 theta = np.linspace(0, tot_angle, tot_points, endpoint=False) 

157 else: 

158 theta = np.linspace(0 + np.pi, tot_angle + np.pi, tot_points, endpoint=False) # this rotates terminals for opposite inclination to be on the opposite side 

159 if far: 

160 theta = theta[1:] # give all points but the first one, so the far surface can be created 

161 else: 

162 theta = theta[:-1] # give all the points but the last one so a near surface can be created 

163 if theta.size < 2: 

164 raise ValueError(f'Combination of number of division points per turn (ndpts) and number of turns (n_turnss) must result in at least 2 Hexahedrons, but inputs result in only {theta.size}!') 

165 C = r_wm * np.arctan(alpha) * np.cos(theta - theta_0) + w_i / (2 * np.pi) 

166 D = np.sqrt(r_wm ** 2 + np.square(C)) 

167 # -- x, y, z points coordinates 

168 xs = (r_wm + hh) * np.cos(theta) - np.sin(theta) * hw * C / D 

169 ys = (r_wm + hh) * np.sin(theta) + np.cos(theta) * hw * C / D 

170 zs = r_wm * np.sin(theta - theta_0) / np.tan(alpha) + w_i * (theta - theta_0) / (2 * np.pi) + z_0 - hw * r_wm / D # original formula 

171 if alpha > 0: 

172 channel_turn = np.ceil((theta - 2 * np.pi / (nept + 1)) / (2 * np.pi)) 

173 # channel_turn[0] = channel_turn[1] # make the hex at theta 0 to belong to the first turn. 

174 else: 

175 channel_turn = np.ceil((theta - np.pi - 2 * np.pi / (nept + 1)) / (2 * np.pi)) 

176 channel_turn[0] = channel_turn[1] # make the hex at theta 0 to belong to the first turn. 

177 return xs.tolist(), ys.tolist(), zs.tolist(), list(map(int, channel_turn)) 

178 

179 def _center_z_and_offset(self, offset=0): 

180 z_mins = [] 

181 z_maxs = [] 

182 for c_val in self._corner_points.values(): 

183 z_mins.append(np.min(c_val['z'])) 

184 z_maxs.append(np.max(c_val['z'])) 

185 z_min_wind = np.min(np.array(z_mins)) 

186 z_max_wind = np.max(np.array(z_maxs)) 

187 z_centre = (z_min_wind + z_max_wind) / 2 

188 if not self.post_proc: 

189 for c_key, c_val in self._corner_points.items(): 

190 self._corner_points[c_key]['z'] = (c_val['z'] - z_centre + offset).tolist() 

191 z_mins = [] 

192 z_maxs = [] 

193 for c_val in self._corner_points.values(): 

194 z_mins.append(np.min(c_val['z'])) 

195 z_maxs.append(np.max(c_val['z'])) 

196 self.z_min_winding_layer = np.min(np.array(z_mins)) 

197 self.z_max_winding_layer = np.max(np.array(z_maxs)) 

198 return z_centre + offset 

199 

200 

201class FQPL(Hexahedrons): 

202 def __init__(self, cctdm, layer): 

203 """ 

204 FQPL hex generator 

205 :param cctdm: cct data model object parsed from yaml input file 

206 :param layer: loop number, integer 

207 """ 

208 super(FQPL, self).__init__() 

209 self.cctdm = cctdm 

210 self.layer = layer 

211 self._calc_z_unit_len() 

212 self._populate_corners() 

213 self._generate_hexes() 

214 self._generate_fqpl() 

215 self._rotate_fqpl() 

216 self._reorder_and_renumber_hexes() 

217 self._clean_attr() 

218 

219 def _calc_z_unit_len(self): 

220 if self.cctdm.geometry.fqpls.z_starts[self.layer] == 'z_min': 

221 self.near = self.cctdm.geometry.air.z_min 

222 self.z_unit_len = (self.cctdm.geometry.fqpls.z_ends[self.layer] - self.near - self.cctdm.geometry.fqpls.fwws[self.layer]) / self.cctdm.geometry.fqpls.fndpls[self.layer] 

223 

224 elif self.cctdm.geometry.fqpls.z_starts[self.layer] == 'z_max': 

225 self.near = self.cctdm.geometry.air.z_max 

226 self.z_unit_len = (self.near - self.cctdm.geometry.fqpls.z_ends[self.layer] - self.cctdm.geometry.fqpls.fwws[self.layer]) / self.cctdm.geometry.fqpls.fndpls[self.layer] 

227 else: 

228 raise ValueError(f'fqpl.z_starts parameter must be a string equal to z_min or z_max, but {self.cctdm.geometry.fqpls.z_starts[self.layer]} was given!') 

229 

230 self.extrusion_sign = math.copysign(1, self.cctdm.geometry.fqpls.z_ends[self.layer] - self.near) 

231 

232 def _calc_first_corner_coords(self, w_s, h_s, far, offset): 

233 x = [self.cctdm.geometry.fqpls.r_ins[self.layer] + self.cctdm.geometry.fqpls.fwhs[self.layer] * h_s] # not rotated coordinate 

234 y = [self.cctdm.geometry.fqpls.fwws[self.layer] * w_s] # not rotated coordinate 

235 if far: 

236 z = [self.near - self.z_unit_len + offset] 

237 else: 

238 z = [self.near + offset] 

239 return x, y, z, [1] 

240 

241 def _populate_corners(self): 

242 r""" 

243 Generates hexahedron with 8 corners numbered 0-7. The size of hexahedron is taken from groove size of cct as specified in yaml. The length is from number of elements per turn. 

244 y (v) 

245 3----------2 

246 |\ ^ |\ 

247 | \ | | \ 

248 | \ | | \ 

249 | 7------+---6 

250 | | +-- |-- | -> x (u) 

251 0---+---\--1 | 

252 \ | \ \ | 

253 \ | \ \ | 

254 \| z(w)\| 

255 4----------5 

256 

257 :return: none 

258 """ 

259 if self.extrusion_sign > 0: 

260 offset = self.z_unit_len 

261 corner_seq = { 

262 0: {'w_s': -0.5, 'h_s': 0, 'far': True}, # 'blcf' 

263 1: {'w_s': -0.5, 'h_s': 1, 'far': True}, # 'tlcf' 

264 2: {'w_s': 0.5, 'h_s': 1, 'far': True}, # 'trcf' 

265 3: {'w_s': 0.5, 'h_s': 0, 'far': True}, # 'brcf' 

266 4: {'w_s': -0.5, 'h_s': 0, 'far': False}, # bottom left corner close #'blcc' 

267 5: {'w_s': -0.5, 'h_s': 1, 'far': False}, # top left corner close #'tlcc' 

268 6: {'w_s': 0.5, 'h_s': 1, 'far': False}, # top right corner close #'trcc' 

269 7: {'w_s': 0.5, 'h_s': 0, 'far': False}, # bottom right corner close #'brcc' 

270 } 

271 else: 

272 offset = 0 

273 corner_seq = { 

274 0: {'w_s': -0.5, 'h_s': 0, 'far': False}, # 'blcf' 

275 1: {'w_s': -0.5, 'h_s': 1, 'far': False}, # 'tlcf' 

276 2: {'w_s': 0.5, 'h_s': 1, 'far': False}, # 'trcf' 

277 3: {'w_s': 0.5, 'h_s': 0, 'far': False}, # 'brcf' 

278 4: {'w_s': -0.5, 'h_s': 0, 'far': True}, # bottom left corner close #'blcc' 

279 5: {'w_s': -0.5, 'h_s': 1, 'far': True}, # top left corner close #'tlcc' 

280 6: {'w_s': 0.5, 'h_s': 1, 'far': True}, # top right corner close #'trcc' 

281 7: {'w_s': 0.5, 'h_s': 0, 'far': True}, # bottom right corner close #'brcc' 

282 } 

283 for corner_num, corner_dict in corner_seq.items(): 

284 self._add_corner_points(corner_num, *self._calc_first_corner_coords(corner_dict['w_s'], corner_dict['h_s'], corner_dict['far'], offset)) 

285 

286 def _get_ref_point(self, elem_number, surf_n_dir_str, coord, dist): 

287 """ 

288 Gets center point for a surface 'pointing' in the direction surf_n_dir_str of hex with elem_number. It then moves that point into along coordinate coord and by distance dist 

289 :param elem_number: hex number to take the surface from 

290 :param surf_n_dir_str: 'direction of surface' (kind of normal), options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+' 

291 :param coord: direction to more the point in, options are: 'x', 'y', 'z' 

292 :param dist: distance to move the point, this is positive or negative float 

293 :return: 

294 """ 

295 source_points = self.vertices_to_surf[self._sur_names.index(surf_n_dir_str)] # get the indexes 4 points for the surface points in direction surf_n_dir_str 

296 points_coor = [self.hexes[elem_number][point] for point in source_points] # get the points, they contain coordinates 

297 xs = [] 

298 ys = [] 

299 zs = [] 

300 for point in points_coor: # get coordinates into list for each axis 

301 for cor_list, coor in zip([xs, ys, zs], ['x', 'y', 'z']): 

302 cor_list.append(point[coor]) 

303 p = {'x': np.mean(xs), 'y': np.mean(ys), 'z': np.mean(zs)} # get averages of each axis coordinate, giving the center of the surface 

304 p[coord] = p[coord] + dist 

305 return p 

306 

307 def _add_elem_with_rot(self, elem_number, surf_n_dir_str, add_ax, angle, ref_p_coor): 

308 """ 

309 :param elem_number: idx of start element 

310 :param surf_n_dir_str: direction of adding element, options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+' 

311 :param ref_p_coor: length of new element in the direction specified above [m] 

312 :return: index of created element 

313 """ 

314 mod_coor = [surf_n_dir_str[0], add_ax] 

315 # mod_coor.remove(surf_n_dir_str[0]) # get the other two coordinates that arenet the direction of the surface to take 

316 

317 angle_rad = np.deg2rad(angle) 

318 op_dir = surf_n_dir_str[0] + self._op_sign[surf_n_dir_str[1]] 

319 new_elem = elem_number + math.copysign(1, elem_number) 

320 self.hexes[new_elem] = {} 

321 source_points = self.vertices_to_surf[self._sur_names.index(surf_n_dir_str)] 

322 destination_points = self.vertices_to_surf[self._sur_names.index(op_dir)] 

323 for d_p, s_p in zip(destination_points + source_points, source_points + source_points): 

324 self.hexes[new_elem][d_p] = copy.deepcopy(self.hexes[elem_number][s_p]) 

325 for s_p in source_points: # modif_points: 

326 v1 = self.hexes[new_elem][s_p][mod_coor[0]] 

327 v2 = self.hexes[new_elem][s_p][mod_coor[1]] 

328 v1_ofset = ref_p_coor[mod_coor[0]] 

329 v2_ofset = ref_p_coor[mod_coor[1]] 

330 v1 = v1 - v1_ofset 

331 v2 = v2 - v2_ofset 

332 v1_new = np.cos(angle_rad) * v1 - np.sin(angle_rad) * v2 

333 v2_new = np.sin(angle_rad) * v1 + np.cos(angle_rad) * v2 

334 v1 = v1_new + v1_ofset 

335 v2 = v2_new + v2_ofset 

336 self.hexes[new_elem][s_p][mod_coor[0]] = v1 

337 self.hexes[new_elem][s_p][mod_coor[1]] = v2 

338 self.hexes[new_elem]['ct'] = self.hexes[elem_number]['ct'] 

339 return new_elem 

340 

341 def _generate_fqpl(self): 

342 new_elem = self._add_elem_to_elem(0, 'z+', self.extrusion_sign * self.z_unit_len) 

343 for _ in range(self.cctdm.geometry.fqpls.fndpls[self.layer] - 2): 

344 new_elem = self._add_elem_to_elem(new_elem, 'z+', self.extrusion_sign * self.z_unit_len) 

345 coord = 'x' 

346 ref_point = self._get_ref_point(new_elem, 'z+', coord, self.cctdm.geometry.fqpls.r_bs[self.layer]) # centre of rotation pint. 

347 n_seg = 10 

348 angle = 180 / n_seg 

349 for _ in range(n_seg): 

350 new_elem = self._add_elem_with_rot(new_elem, 'z+', coord, self.extrusion_sign * angle, ref_point) 

351 for _ in range(self.cctdm.geometry.fqpls.fndpls[self.layer]): 

352 new_elem = self._add_elem_to_elem(new_elem, 'z+', -self.extrusion_sign * self.z_unit_len) 

353 

354 def _rotate_fqpl(self): 

355 for h_num, h_dict in self.hexes.items(): 

356 channel_turn = h_dict.pop('ct', None) 

357 for p_num, p_dict in h_dict.items(): 

358 xx = p_dict['x'] 

359 yy = p_dict['y'] 

360 x = xx * np.cos(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) - yy * np.sin(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) # rotated coordinate 

361 y = xx * np.sin(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) + yy * np.cos(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) # rotated coordinate 

362 self.hexes[h_num][p_num]['x'] = x 

363 self.hexes[h_num][p_num]['y'] = y 

364 self.hexes[h_num]['ct'] = channel_turn 

365 

366 def _clean_attr(self): 

367 del self.cctdm 

368 del self.layer 

369 del self.near 

370 del self.z_unit_len 

371 

372 

373class Generate_BREPs: 

374 def __init__(self, fdm, verbose=True): 

375 """ 

376 Class to generate (build) Windings and Formers (WF) of a canted cosine theta (CCT) magnet and save them to brep files 

377 :param fdm: fiqus data model 

378 :param verbose: If True more information is printed in python console. 

379 """ 

380 self.cctdm = fdm.magnet 

381 self.model_folder = os.path.join(os.getcwd()) 

382 self.magnet_name = fdm.general.magnet_name 

383 

384 self.verbose = verbose 

385 self.cctwi = WindingsInformation() 

386 self.cctwi.magnet_name = self.magnet_name 

387 self.transl_dict = {'vol': '', 'surf': '_bd', 'surf_in': '_in', 'surf_out': '_out', 'cochain': '_cut'} 

388 self.gu = GmshUtils(self.model_folder, self.verbose) 

389 self.gu.initialize() 

390 self.formers = [] 

391 self.air = [] 

392 gmsh.option.setString('Geometry.OCCTargetUnit', 'M') 

393 

394 def generate_windings_or_fqpls(self, type_str): 

395 """ 

396 Generates windings brep files, as many as number of entries in the yaml file for windings. 

397 :param type_str: What to generate, options are: 'windings' or 'fqpls' 

398 :return: None, saves brep to disk 

399 """ 

400 if self.verbose: 

401 print(f'Generating {type_str} started') 

402 start_time = timeit.default_timer() 

403 

404 def generate_geom(worf_in, names_in): 

405 worf_tags_dict_in = {} 

406 for worf_num, worf_in in enumerate(worf_in): 

407 worf_tags_dict_in[worf_num] = self.generate_hex_geom(worf_in) 

408 gmsh.model.occ.synchronize() 

409 gmsh.write(os.path.join(self.model_folder, f'{names_in[worf_num]}.brep')) 

410 gmsh.clear() 

411 return worf_tags_dict_in 

412 

413 if self.cctdm.geometry.windings.names and type_str == 'windings': 

414 worf = [Winding(self.cctdm, i) for i, _ in enumerate(self.cctdm.geometry.windings.names)] # worf = winding or fqpl 

415 names = [name for name in self.cctdm.geometry.windings.names] 

416 for w in worf: 

417 if self.cctdm.geometry.air.z_min > w.z_min_winding_layer or w.z_max_winding_layer > self.cctdm.geometry.air.z_max: 

418 raise ValueError(f'{self.cctdm.geometry.air.name} region dimensions are too s mall to fit the windings turns. Please extend z_min and/or z_max') 

419 else: 

420 pass 

421 worf_tags_dict = generate_geom(worf, names) 

422 

423 if self.cctdm.geometry.fqpls.names and type_str == 'fqpls': 

424 worf = [FQPL(self.cctdm, i) for i, _ in enumerate(self.cctdm.geometry.fqpls.names)] # worf = winding or fqpl 

425 names = [name for name in self.cctdm.geometry.fqpls.names] 

426 worf_tags_dict = generate_geom(worf, names) 

427 

428 w_z_maxs = [] 

429 w_z_mins = [] 

430 if type_str == 'windings': 

431 for winding in worf: 

432 w_z_maxs.append(winding.z_max_winding_layer) 

433 w_z_mins.append(winding.z_min_winding_layer) 

434 self.cctwi.windings_avg_length = float(np.mean(w_z_maxs) - np.mean(w_z_mins)) 

435 self.cctwi.windings.names = names 

436 self.cctwi.windings.t_in.vol_st = [] 

437 self.cctwi.windings.t_in.surf_st = [] 

438 self.cctwi.windings.t_out.vol_st = [] 

439 self.cctwi.windings.t_out.surf_st = [] 

440 self.cctwi.windings.t_in.vol_et = [] 

441 self.cctwi.windings.t_in.surf_et = [] 

442 self.cctwi.windings.t_out.vol_et = [] 

443 self.cctwi.windings.t_out.surf_et = [] 

444 self.cctwi.windings.t_in.lc_et = [] 

445 self.cctwi.windings.t_out.lc_et = [] 

446 self.cctwi.windings.t_in.ndpterms = self.cctdm.geometry.windings.ndpt_ins 

447 self.cctwi.windings.t_out.ndpterms = self.cctdm.geometry.windings.ndpt_outs 

448 self.cctwi.windings.t_in.z_air = self.cctdm.geometry.air.z_min 

449 self.cctwi.windings.t_out.z_air = self.cctdm.geometry.air.z_max 

450 for winding_num, winding_dict in worf_tags_dict.items(): 

451 first_vol = list(winding_dict.keys())[0] 

452 last_vol = list(winding_dict.keys())[-1] 

453 self.cctwi.windings.t_in.vol_st.append(winding_dict[first_vol]['v_t']) 

454 self.cctwi.windings.t_in.surf_st.append(winding_dict[first_vol]['sf_ts'][0]) 

455 self.cctwi.windings.t_out.vol_st.append(winding_dict[last_vol]['v_t']) 

456 self.cctwi.windings.t_out.surf_st.append(winding_dict[last_vol]['sf_ts'][2]) 

457 self.cctwi.windings.t_in.vol_et.append(winding_dict[first_vol]['v_t']) 

458 self.cctwi.windings.t_in.surf_et.append(winding_dict[first_vol]['sf_ts'][0]) 

459 self.cctwi.windings.t_out.vol_et.append(winding_dict[last_vol]['v_t']) 

460 self.cctwi.windings.t_out.surf_et.append(winding_dict[last_vol]['sf_ts'][2]) 

461 

462 lc_st_neg_alpha = [[1, 0], [0, 3], [3, 2], [2, 1], [6, 4], [4, 5], [5, 7], [7, 6], [1, 6], [0, 4], [3, 5], [2, 7]] 

463 lc_st_pos_alpha = [[3, 1], [1, 0], [0, 2], [2, 3], [7, 4], [4, 5], [5, 6], [6, 7], [3, 7], [1, 4], [0, 5], [2, 6]] 

464 lc_et_neg_alpha = [[5, 7], [7, 6], [6, 4], [4, 5], [1, 3], [3, 2], [2, 0], [0, 1], [5, 1], [7, 3], [6, 2], [4, 0]] 

465 lc_et_pos_alpha = [[5, 7], [7, 6], [6, 4], [4, 5], [1, 3], [3, 2], [2, 0], [0, 1], [5, 1], [7, 3], [6, 2], [4, 0]] 

466 # [[1, 2], [2, 3], [3, 4], [4, 1], [5, 6], [6, 7], [7, 8], [8, 5], [1, 5], [2, 6], [3, 7], [4, 8]] 

467 # [[2, 1], [1, 4], [4, 3], [3, 2], [6, 5], [5, 8], [8, 7], [7, 6], [2, 6], [1, 5], [4, 8], [3, 7]] 

468 

469 lc_et_pos_corr = [[1, 0], [0, 3], [3, 2], [2, 1], [5, 4], [4, 7], [7, 6], [6, 5], [1, 5], [0, 4], [3, 7], [2, 6]] 

470 

471 self.cctwi.windings.t_in.lc_st = [] 

472 self.cctwi.windings.t_out.lc_st = [] 

473 for alpha in self.cctdm.geometry.windings.alphas: 

474 if alpha > 0: 

475 self.cctwi.windings.t_in.lc_st.append(lc_st_pos_alpha) 

476 self.cctwi.windings.t_out.lc_st.append(lc_st_pos_alpha) 

477 self.cctwi.windings.t_in.lc_et.append(lc_et_pos_corr) 

478 self.cctwi.windings.t_out.lc_et.append(lc_et_pos_alpha) 

479 else: 

480 self.cctwi.windings.t_in.lc_st.append(lc_st_neg_alpha) 

481 self.cctwi.windings.t_out.lc_st.append(lc_st_neg_alpha) 

482 self.cctwi.windings.t_in.lc_et.append(lc_et_neg_alpha) 

483 self.cctwi.windings.t_out.lc_et.append(lc_et_pos_corr) 

484 

485 if self.verbose: 

486 print(f'Generating {type_str} took {timeit.default_timer() - start_time:.2f} s') 

487 

488 def save_volume_info(self): 

489 if self.cctdm.geometry.fqpls.names: 

490 fqpls = self.cctdm.geometry.fqpls.names 

491 else: 

492 fqpls = [] 

493 

494 self.cctwi.w_names = self.cctdm.geometry.windings.names 

495 self.cctwi.f_names = fqpls 

496 self.cctwi.formers = self.cctdm.geometry.formers.names 

497 self.cctwi.air = self.cctdm.geometry.air.name 

498 

499 volume_info_file = os.path.join(self.model_folder, f'{self.cctwi.magnet_name}.wi') 

500 uff.write_data_to_yaml(volume_info_file, self.cctwi.dict()) 

501 

502 def generate_formers(self): 

503 if self.cctdm.geometry.formers.r_ins: 

504 if self.verbose: 

505 print('Generating Formers Started') 

506 start_time = timeit.default_timer() 

507 

508 for f_i, _ in enumerate(self.cctdm.geometry.formers.r_ins): 

509 z = self.cctdm.geometry.formers.z_mins[f_i] 

510 dz = self.cctdm.geometry.formers.z_maxs[f_i] - self.cctdm.geometry.formers.z_mins[f_i] 

511 cylin_out = (3, gmsh.model.occ.addCylinder(0, 0, z, 0, 0, dz, self.cctdm.geometry.formers.r_outs[f_i], angle=2 * math.pi)) # add cylinder to align to existing bodies in the pro file 

512 cylin_in = (3, gmsh.model.occ.addCylinder(0, 0, z, 0, 0, dz, self.cctdm.geometry.formers.r_ins[f_i], angle=2 * math.pi)) # add another cylinder to subtract from the first one to make a tube 

513 cylinder = gmsh.model.occ.cut([cylin_out], [cylin_in], removeObject=True) # subtract cylinders to make a tube 

514 self.formers.append(cylinder[0][0]) # keep just the tag and append 

515 gmsh.model.occ.synchronize() 

516 gmsh.write(os.path.join(self.model_folder, f'{self.cctdm.geometry.formers.names[f_i]}.brep')) 

517 gmsh.clear() 

518 if self.verbose: 

519 print(f'Generating formers took {timeit.default_timer() - start_time:.2f} s') 

520 

521 def generate_air(self): 

522 if self.cctdm.geometry.air.ar: 

523 if self.verbose: 

524 print('Generating air started') 

525 start_time = timeit.default_timer() 

526 if self.cctdm.geometry.air.sh_type == 'cylinder': 

527 self.air = [(3, gmsh.model.occ.addCylinder(0, 0, self.cctdm.geometry.air.z_min, 0, 0, self.cctdm.geometry.air.z_max - self.cctdm.geometry.air.z_min, self.cctdm.geometry.air.ar, angle=2 * math.pi))] 

528 elif self.cctdm.geometry.air.sh_type == 'cuboid': 

529 air_box_size = [-self.cctdm.geometry.air.ar / 2, -self.cctdm.geometry.air.ar / 2, self.cctdm.geometry.air.z_min, self.cctdm.geometry.air.ar, self.cctdm.geometry.air.ar, 

530 self.cctdm.geometry.air.z_max - self.cctdm.geometry.air.z_min] # list of box size with: x, y, z, dx, dy, dz 

531 self.air = [(3, gmsh.model.occ.addBox(*air_box_size))] 

532 else: 

533 raise ValueError(f'Shape type: {self.cctdm.geometry.air.sh_type} is not supported!') 

534 gmsh.model.occ.synchronize() 

535 gmsh.write(os.path.join(self.model_folder, f'{self.cctdm.geometry.air.name}.brep')) 

536 gmsh.clear() 

537 if self.verbose: 

538 print(f'Generating air took {timeit.default_timer() - start_time:.2f} s') 

539 

540 def generate_regions_file(self): 

541 if self.verbose: 

542 print('Generating Regions File Started') 

543 start_time = timeit.default_timer() 

544 cctrm = Reg_Mod_FiQ.RegionsModel() 

545 vrt = 1000000 # volume region tag start 

546 srt = 2000000 # surface region tag start 

547 lrt = 3000000 # line region tag start 

548 # -------- powered ---------- 

549 # volumes 

550 cctrm.powered.vol.names = [name + self.transl_dict['vol'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names] 

551 cctrm.powered.vol.currents = self.cctdm.solve.windings.currents + self.cctdm.solve.fqpls.currents 

552 cctrm.powered.vol.sigmas = self.cctdm.solve.windings.sigmas + self.cctdm.solve.fqpls.sigmas 

553 cctrm.powered.vol.mu_rs = self.cctdm.solve.windings.mu_rs + self.cctdm.solve.fqpls.mu_rs 

554 reg = [] 

555 for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names: 

556 reg.append(vrt) 

557 vrt += 1 

558 cctrm.powered.vol.numbers = reg 

559 # surfaces 

560 cctrm.powered.surf_in.names = [name + self.transl_dict['surf_in'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names] 

561 cctrm.powered.surf_out.names = [name + self.transl_dict['surf_out'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names] 

562 reg = [] 

563 for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names: 

564 reg.append(srt) 

565 srt += 1 

566 cctrm.powered.surf_in.numbers = reg 

567 reg = [] 

568 for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names: 

569 reg.append(srt) 

570 srt += 1 

571 cctrm.powered.surf_out.numbers = reg 

572 

573 # -------- induced ---------- 

574 # volumes 

575 cctrm.induced.vol.names = [name + self.transl_dict['vol'] for name in self.cctdm.geometry.formers.names] 

576 cctrm.induced.vol.sigmas = self.cctdm.solve.formers.sigmas 

577 cctrm.induced.vol.mu_rs = self.cctdm.solve.formers.mu_rs 

578 reg = [] 

579 for _ in self.cctdm.geometry.formers.names: 

580 reg.append(vrt) 

581 vrt += 1 

582 cctrm.induced.vol.numbers = reg 

583 

584 # -------- air ---------- 

585 # volumes 

586 cctrm.air.vol.name = self.cctdm.geometry.air.name + self.transl_dict['vol'] 

587 cctrm.air.vol.sigma = self.cctdm.solve.air.sigma 

588 cctrm.air.vol.mu_r = self.cctdm.solve.air.mu_r 

589 cctrm.air.vol.number = vrt 

590 vrt += 1 

591 

592 # surface 

593 cctrm.air.surf.name = self.cctdm.geometry.air.name + self.transl_dict['surf'] 

594 cctrm.air.surf.number = srt 

595 srt += 1 

596 

597 # # center line 

598 # cctrm.air.line.name = 'Center_line' 

599 # cctrm.air.line.number = lrt 

600 # lrt += 1 

601 lrt = srt 

602 

603 # --------- cuts ------- 

604 # these need to be done at the end with the highest surface tags 

605 cctrm.powered.cochain.names = [name + self.transl_dict['cochain'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names] 

606 reg = [] 

607 for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names: 

608 reg.append(lrt) 

609 lrt += 1 

610 cctrm.powered.cochain.numbers = reg 

611 # induced cuts 

612 cctrm.induced.cochain.names = [name + self.transl_dict['cochain'] for name in self.cctdm.geometry.formers.names] 

613 reg = [] 

614 for _ in self.cctdm.geometry.formers.names: 

615 reg.append(lrt) 

616 lrt += 1 

617 cctrm.induced.cochain.numbers = reg 

618 

619 uff.write_data_to_yaml(os.path.join(self.model_folder, f'{self.cctwi.magnet_name}.regions'), cctrm.dict()) 

620 

621 if self.verbose: 

622 print(f'Generating Regions File Took {timeit.default_timer() - start_time:.2f} s') 

623 

624 @staticmethod 

625 def generate_hex_geom(hexes_dict, 

626 cons_for_lines=[[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]], 

627 vol_tag=-1): 

628 """ 

629 Generates hexahedra volumes from data in hexes dict 

630 :param hexes_dict: dictionary of data generated by either Winding or FQPL class in Hexahedrons.py 

631 :param cons_for_lines: line connection defined as list of list in terms of point numbers to connect in the hexahedron 

632 :param vol_tag: volume tag to use for generated volume of hexahedron 

633 :return: dictionary with tags of created volumes, surface loops, surface fillings, closed loops, lines, points and channel turn (i.e. turn number of CCT winding). 

634 """ 

635 tags_dict = {} 

636 for h_num, h_dict in hexes_dict.hexes.items(): 

637 p_ts = [] # Point tags 

638 channel_turn = h_dict.pop('ct', None) 

639 for p_num, p_dict in h_dict.items(): 

640 p_ts.append(gmsh.model.occ.addPoint(p_dict['x'], p_dict['y'], p_dict['z'])) 

641 

642 l_ts = [] # Line tags 

643 for p_c in cons_for_lines: # point connections to make a line 

644 l_ts.append(gmsh.model.occ.addLine(p_ts[p_c[0]], p_ts[p_c[1]])) 

645 

646 cl_ts = [] # Curved Loops tags 

647 for l_c in [[0, 1, 2, 3], [4, 5, 6, 7], [3, 8, 7, 11], [1, 9, 5, 10], [0, 8, 4, 9], [2, 11, 6, 10]]: # line connections to make a curved loop 

648 cl_ts.append(gmsh.model.occ.addCurveLoop([l_ts[l_c[0]], l_ts[l_c[1]], l_ts[l_c[2]], l_ts[l_c[3]]])) 

649 sf_ts = [] # Surface Filling tags 

650 for cl_t in cl_ts: 

651 sf_ts.append(gmsh.model.occ.addSurfaceFilling(cl_t, degree=3, tol2d=0.000001, tol3d=0.00001, tolAng=0.001, tolCurv=0.05)) 

652 sl_t = gmsh.model.occ.addSurfaceLoop(sf_ts, sewing=True) 

653 v_t = gmsh.model.occ.addVolume([sl_t], vol_tag) 

654 tags_dict[int(h_num)] = {'v_t': v_t, 'sl_t': sl_t, 'sf_ts': sf_ts, 'cl_ts': cl_ts, 'l_ts': l_ts, 'p_ts': p_ts, 'ct': channel_turn} 

655 return tags_dict 

656 

657 

658class Prepare_BREPs: 

659 def __init__(self, fdm, verbose=True) -> object: 

660 """ 

661 Class to preparing brep files by adding terminals. 

662 :param fdm: FiQuS data model 

663 :param verbose: If True more information is printed in python console. 

664 """ 

665 

666 self.cctdm = fdm.magnet 

667 self.model_folder = os.path.join(os.getcwd()) 

668 self.magnet_name = fdm.general.magnet_name 

669 

670 self.verbose = verbose 

671 self.winding_info_file = os.path.join(self.model_folder, f'{self.magnet_name}.wi') 

672 self.cctwi = uff.read_data_from_yaml(self.winding_info_file, WindingsInformation) 

673 self.gu = GmshUtils(self.model_folder, self.verbose) 

674 self.gu.initialize() 

675 self.model_file = os.path.join(self.model_folder, f'{self.magnet_name}.brep') 

676 

677 @staticmethod 

678 def get_pts_for_sts(surface_tags): 

679 """ 

680 Get point tags for surface tags 

681 :param surface_tags: list of surface tags to get point tags 

682 :return: list of point tags belonging to the surfaces in the list 

683 """ 

684 vol_line_tags = [] 

685 vol_line_tags_i = 0 

686 vol_line_tags_idx = [] 

687 for surf_tag in surface_tags: 

688 line_tags_new = gmsh.model.getAdjacencies(2, surf_tag)[1] 

689 for line_tag in line_tags_new: 

690 vol_line_tags_i += 1 

691 if line_tag not in vol_line_tags: 

692 vol_line_tags.append(line_tag) 

693 vol_line_tags_idx.append(vol_line_tags_i) 

694 point_tags = [] 

695 point_tags_i = 0 

696 point_tags_idx = [] 

697 for line_tag in vol_line_tags: 

698 point_tags_new = gmsh.model.getAdjacencies(1, line_tag)[1] 

699 for point_tag in point_tags_new: 

700 point_tags_i += 1 

701 if point_tag not in point_tags: 

702 point_tags.append(point_tag) 

703 point_tags_idx.append(point_tags_i) 

704 return point_tags, vol_line_tags_idx, point_tags_idx 

705 

706 def straighten_terminal(self, gui=False): 

707 """ 

708 Extends winding geom_generators to the air region boundary by extending the geom_generators with 'terminals' geom_generators up to the air boundary surfaces 

709 By default this extends along the z axis and makes the final surface normal to the z axis. 

710 :return: None, saves breps with straighten terminals to disk 

711 """ 

712 

713 for i, name in enumerate(self.cctwi.windings.names): 

714 if self.verbose: 

715 print(f'Straightening terminals of {name} started') 

716 start_time = timeit.default_timer() 

717 gmsh.open(os.path.join(self.model_folder, f'{name}.brep')) 

718 for terminal in [self.cctwi.windings.t_in, self.cctwi.windings.t_out]: 

719 vol_tag = terminal.vol_st[i] 

720 vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1] 

721 surf_tag = terminal.surf_st[i] 

722 if surf_tag not in vol_surf_tags: 

723 raise ValueError(f'Surface tag of {surf_tag} given for volume in of {vol_tag} does not belong to the volume!') 

724 # surf_point_tags = self.get_pts_for_sts([surf_tag]) # only 'powered' surface 

725 surf_point_tags, surf_line_tags_idx, surf_point_tags_idx = self.get_pts_for_sts([surf_tag]) # only 'powered' surface 

726 vol_point_tags, vol_line_tags_idx, vol_point_tags_idx = self.get_pts_for_sts(vol_surf_tags) # all tags of hex 

727 vol_point_dict = {} 

728 for p_tag in vol_point_tags: 

729 xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag) 

730 vol_point_dict[p_tag] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2} 

731 z = [] 

732 y = [] 

733 x = [] 

734 for p_tag in surf_point_tags: # calculate average z position for the terminal surface 

735 xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag) 

736 x.append((xmin + xmax) / 2) 

737 y.append((ymin + ymax) / 2) 

738 z.append((zmin + zmax) / 2) 

739 x_avg = np.mean(x) 

740 y_avg = np.mean(y) 

741 z_avg = np.mean(z) 

742 sign_x = [math.copysign(1, x_i - x_avg) for x_i in x] 

743 sign_y = [math.copysign(1, y_i - y_avg) for y_i in y] 

744 dist_x = [] 

745 dist_y = [] 

746 for j in range(len(x) - 1): 

747 dist_x.append(math.sqrt((x[j] - x[j + 1]) ** 2)) 

748 dist_y.append(math.sqrt((y[j] - y[j + 1]) ** 2)) 

749 eq_len = self.cctdm.geometry.windings.wwhs[i] / 2 

750 y_shift_sign = math.copysign(1, -terminal.z_air * self.cctdm.geometry.windings.alphas[i]) 

751 new_x = [x_avg + s_x * eq_len for s_x in sign_x] 

752 new_y = [y_avg + s_y * eq_len + y_shift_sign * eq_len - y_shift_sign * dist_y[0] / 2 for s_y in sign_y] 

753 for p_tag, x_n, y_n in zip(surf_point_tags, new_x, new_y): # assign z_avg to only points on the terminal surface 

754 vol_point_dict[p_tag]['x'] = x_n 

755 vol_point_dict[p_tag]['y'] = y_n 

756 vol_point_dict[p_tag]['z'] = z_avg 

757 gmsh.model.occ.remove([(3, terminal.vol_st[i])], recursive=True) 

758 gmsh.model.occ.synchronize() 

759 hexes = Hexahedrons() # create hex class instance to be able to reuse existing code for generating hexes geom_generators 

760 hexes.hexes[0] = {} # only one winding layer is involved per iteration, so hardcoded 0 index 

761 for p_i, (_, p_dict) in enumerate(vol_point_dict.items()): 

762 hexes.hexes[0][p_i] = p_dict 

763 Generate_BREPs.generate_hex_geom(hexes, cons_for_lines=terminal.lc_st[i], vol_tag=terminal.vol_st[i]) 

764 gmsh.model.occ.synchronize() 

765 gmsh.write(os.path.join(self.model_folder, f'{name}.brep')) 

766 if self.verbose: 

767 print(f'Straightening terminals of {name} took {timeit.default_timer() - start_time:.2f} s') 

768 # uff.write_data_to_yaml(self.winding_info_yaml_path, self.cctvi.dict()) 

769 if gui: 

770 gmsh.model.occ.synchronize() 

771 self.gu.launch_interactive_GUI() 

772 else: 

773 gmsh.clear() 

774 

775 def extend_terms(self, operation='extend', gui=False, ): 

776 self.cctwi = uff.read_data_from_yaml(self.winding_info_file, WindingsInformation) # this is repeated as the operation over the data for the case add, so an original data for extend operation needs to be loaded. 

777 if operation == 'add': 

778 for terminal in [self.cctwi.windings.t_in, self.cctwi.windings.t_out]: 

779 terminal.ndpterms = [1] * len(terminal.ndpterms) 

780 terminal.z_air = terminal.z_add 

781 elif operation == 'extend': 

782 pass 

783 file_in_postfix = '' 

784 file_out_postfix = '' 

785 for i, name in enumerate(self.cctwi.windings.names): 

786 if self.verbose: 

787 print(f'Extending terminals of {name} started') 

788 start_time = timeit.default_timer() 

789 gmsh.open(os.path.join(self.model_folder, f'{name}{file_in_postfix}.brep')) 

790 volumes = [vol[1] for vol in gmsh.model.getEntities(dim=3)] 

791 for oper, terminal in enumerate([self.cctwi.windings.t_in, self.cctwi.windings.t_out]): 

792 vol_tag = terminal.vol_et[i] 

793 vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1] 

794 surf_tag = terminal.surf_et[i] 

795 if surf_tag not in vol_surf_tags: 

796 raise ValueError(f'Surface tag of {surf_tag} given for volume in of {vol_tag} does not belong to the volume!') 

797 surf_point_tags, surf_line_tags_idx, surf_point_tags_idx = self.get_pts_for_sts([surf_tag]) 

798 surf_point_tags = sorted(surf_point_tags) 

799 vol_point_tags, vol_line_tags_idx, vol_point_tags_idx = self.get_pts_for_sts(vol_surf_tags) 

800 vol_point_tags = sorted(vol_point_tags) 

801 surf_point_tags_idx_pw = [i for i, e in enumerate(vol_point_tags) if e in set(surf_point_tags)] # powered 

802 surf_point_tags_idx_oth = [i for i, e in enumerate(vol_point_tags) if e not in set(surf_point_tags)] # other (opposite powered) 

803 vol_point_dict = {} 

804 for idx_pw, p_tag in zip(surf_point_tags_idx_pw, surf_point_tags): 

805 xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag) 

806 vol_point_dict[idx_pw] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2} 

807 z_ext = round((terminal.z_air - zmin) / terminal.ndpterms[i], 8) 

808 for idx_oth, p_tag in zip(surf_point_tags_idx_oth, surf_point_tags): 

809 xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag) 

810 vol_point_dict[idx_oth] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2 + z_ext} 

811 hexes = Hexahedrons() # create hex class instance to be able to reuse existing code for generating hexes geom_generators 

812 for h in range(terminal.ndpterms[i]): 

813 hexes.hexes[h] = {} 

814 for p_i, (_, p_dict) in enumerate(vol_point_dict.items()): 

815 hexes.hexes[h][p_i] = copy.deepcopy(p_dict) 

816 for p_t, p_dict in vol_point_dict.items(): 

817 p_dict['z'] = p_dict['z'] + z_ext 

818 for idx_oth in range(4, 8): 

819 z_air = terminal.z_air 

820 hexes.hexes[h][idx_oth]['z'] = z_air 

821 tags_dict = Generate_BREPs.generate_hex_geom(hexes, cons_for_lines=terminal.lc_et[i], vol_tag=-1) 

822 new_volumes = [] 

823 for _, hex_dict in tags_dict.items(): 

824 new_volumes.append(hex_dict['v_t']) 

825 gmsh.model.occ.synchronize() 

826 if oper == 0: # this vol_in is needed for reordering volumes later 

827 vol_in = new_volumes 

828 renumbered_vols = [] 

829 other_vols = list(set(volumes) - set(vol_in)) 

830 max_tag = gmsh.model.occ.getMaxTag(3) 

831 for vol in other_vols: 

832 gmsh.model.setTag(3, vol, vol + max_tag) 

833 renumbered_vols.append(vol + max_tag) 

834 for n_tag, vol in enumerate(reversed(vol_in)): 

835 gmsh.model.setTag(3, vol, n_tag + 1) 

836 max_term_tag = len(vol_in) + 1 

837 spiral_volumes = [] 

838 for n_tag, vol in enumerate(renumbered_vols): 

839 gmsh.model.setTag(3, vol, n_tag + max_term_tag) 

840 spiral_volumes.append(n_tag + max_term_tag) 

841 gmsh.write(os.path.join(self.model_folder, f'{name}{file_out_postfix}.brep')) 

842 if self.verbose: 

843 print(f'Straightening terminals of {name} took {timeit.default_timer() - start_time:.2f} s') 

844 print(f'Writing volume information file: {name}.vi ') 

845 vi = {'export': spiral_volumes, 'all': [vol[1] for vol in gmsh.model.getEntities(dim=3)]} 

846 json.dump(vi, open(f"{os.path.join(self.model_folder, name)}.vi", 'w')) 

847 if self.verbose: 

848 print(f'Done writing volume information file: {name}.vi ') 

849 if gui: 

850 self.gu.launch_interactive_GUI() 

851 else: 

852 gmsh.clear() 

853 

854 def save_fqpl_vi(self): 

855 for f_name in self.cctdm.geometry.fqpls.names: 

856 if self.verbose: 

857 print(f'Writing volume information file: {f_name}.vi ') 

858 gmsh.open(os.path.join(self.model_folder, f'{f_name}.brep')) 

859 volumes = [vol[1] for vol in gmsh.model.getEntities(dim=3)] 

860 export = volumes 

861 vi = {'export': export, 'all': volumes} 

862 json.dump(vi, open(f"{os.path.join(self.model_folder, f_name)}.vi", 'w')) 

863 if self.verbose: 

864 print(f'Done writing volume information file: {f_name}.vi ') 

865 gmsh.clear() 

866 

867 def fragment(self, gui=False): 

868 if self.verbose: 

869 print('Loading files in preparation for fragment operation') 

870 for f_name in self.cctwi.w_names + self.cctwi.f_names: 

871 gmsh.merge(os.path.join(self.model_folder, f'{f_name}.brep')) 

872 num_vol = np.max([vol[1] for vol in gmsh.model.getEntities(dim=3)]) 

873 for f_name in self.cctwi.formers: 

874 gmsh.merge(os.path.join(self.model_folder, f'{f_name}.brep')) 

875 num_vol += 1 

876 gmsh.merge(os.path.join(self.model_folder, f'{self.cctwi.air}.brep')) 

877 num_vol += 1 

878 entities = gmsh.model.getEntities(dim=3) 

879 if len(entities) != num_vol: 

880 raise ValueError('Not consistent volumes numbers in brep and json files!') 

881 objectDimTags = [entities[-1]] 

882 toolDimTags = entities[:-1] 

883 # central_line = [(1, 3898)] 

884 # toolDimTags = toolDimTags+central_line 

885 if self.verbose: 

886 print(f'Fragmenting {self.magnet_name} started') 

887 start_time = timeit.default_timer() 

888 gmsh.model.occ.fragment(objectDimTags, toolDimTags, removeObject=True, removeTool=True) 

889 gmsh.model.occ.synchronize() 

890 if self.verbose: 

891 print(f'Fragmenting {self.magnet_name} took {timeit.default_timer() - start_time:.2f} s') 

892 

893 gmsh.write(self.model_file) 

894 if gui: 

895 self.gu.launch_interactive_GUI() 

896 else: 

897 gmsh.clear() 

898 gmsh.finalize() 

899 

900 def load_geometry(self, gui=False): 

901 gmsh.open(self.model_file) 

902 if gui: 

903 self.gu.launch_interactive_GUI()