Coverage for fiqus/geom_generators/GeometryCCT.py: 96%
655 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-01-14 02:37 +0100
« prev ^ index » next coverage.py v7.4.4, created at 2025-01-14 02:37 +0100
1import os
2import copy
3import math
4import collections
5import logging
7import json
8import timeit
9import numpy as np
10import gmsh
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
17logger = logging.getLogger(__name__)
18class Hexahedrons:
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
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: '-'}
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
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]
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
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
79class Winding(Hexahedrons):
81 def __init__(self, cctdm, layer, post_proc=False):
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()
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
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']))
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))
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
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()
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]
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!')
230 self.extrusion_sign = math.copysign(1, self.cctdm.geometry.fqpls.z_ends[self.layer] - self.near)
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]
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
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))
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
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
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
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)
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
366 def _clean_attr(self):
367 del self.cctdm
368 del self.layer
369 del self.near
370 del self.z_unit_len
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
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')
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()
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
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)
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)
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])
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]]
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]]
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)
485 if self.verbose:
486 print(f'Generating {type_str} took {timeit.default_timer() - start_time:.2f} s')
488 def save_volume_info(self):
489 if self.cctdm.geometry.fqpls.names:
490 fqpls = self.cctdm.geometry.fqpls.names
491 else:
492 fqpls = []
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
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())
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()
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')
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')
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['cct'] = Reg_Mod_FiQ.Powered()
551 cctrm.powered['cct'].vol.names = [name + self.transl_dict['vol'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
552 cctrm.powered['cct'].vol.currents = self.cctdm.solve.windings.currents + self.cctdm.solve.fqpls.currents
553 cctrm.powered['cct'].vol.sigmas = self.cctdm.solve.windings.sigmas + self.cctdm.solve.fqpls.sigmas
554 cctrm.powered['cct'].vol.mu_rs = self.cctdm.solve.windings.mu_rs + self.cctdm.solve.fqpls.mu_rs
555 reg = []
556 for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
557 reg.append(vrt)
558 vrt += 1
559 cctrm.powered['cct'].vol.numbers = reg
560 # surfaces
561 cctrm.powered['cct'].surf_in.names = [name + self.transl_dict['surf_in'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
562 cctrm.powered['cct'].surf_out.names = [name + self.transl_dict['surf_out'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
563 reg = []
564 for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
565 reg.append(srt)
566 srt += 1
567 cctrm.powered['cct'].surf_in.numbers = reg
568 reg = []
569 for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
570 reg.append(srt)
571 srt += 1
572 cctrm.powered['cct'].surf_out.numbers = reg
574 # -------- induced ----------
575 # volumes
576 cctrm.induced['cct'] = Reg_Mod_FiQ.Induced()
577 cctrm.induced['cct'].vol.names = [name + self.transl_dict['vol'] for name in self.cctdm.geometry.formers.names]
578 cctrm.induced['cct'].vol.sigmas = self.cctdm.solve.formers.sigmas
579 cctrm.induced['cct'].vol.mu_rs = self.cctdm.solve.formers.mu_rs
580 reg = []
581 for _ in self.cctdm.geometry.formers.names:
582 reg.append(vrt)
583 vrt += 1
584 cctrm.induced['cct'].vol.numbers = reg
586 # -------- air ----------
587 # volumes
588 cctrm.air.vol.name = self.cctdm.geometry.air.name + self.transl_dict['vol']
589 cctrm.air.vol.sigma = self.cctdm.solve.air.sigma
590 cctrm.air.vol.mu_r = self.cctdm.solve.air.mu_r
591 cctrm.air.vol.number = vrt
592 vrt += 1
594 # surface
595 cctrm.air.surf.name = self.cctdm.geometry.air.name + self.transl_dict['surf']
596 cctrm.air.surf.number = srt
597 srt += 1
599 # # center line
600 # cctrm.air.line.name = 'Center_line'
601 # cctrm.air.line.number = lrt
602 # lrt += 1
603 lrt = srt
605 # --------- cuts -------
606 # these need to be done at the end with the highest surface tags
607 cctrm.powered['cct'].cochain.names = [name + self.transl_dict['cochain'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
608 reg = []
609 for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
610 reg.append(lrt)
611 lrt += 1
612 cctrm.powered['cct'].cochain.numbers = reg
613 # induced cuts
614 cctrm.induced['cct'].cochain.names = [name + self.transl_dict['cochain'] for name in self.cctdm.geometry.formers.names]
615 reg = []
616 for _ in self.cctdm.geometry.formers.names:
617 reg.append(lrt)
618 lrt += 1
619 cctrm.induced['cct'].cochain.numbers = reg
621 uff.write_data_to_yaml(os.path.join(self.model_folder, f'{self.cctwi.magnet_name}.regions'), cctrm.dict())
623 if self.verbose:
624 print(f'Generating Regions File Took {timeit.default_timer() - start_time:.2f} s')
626 @staticmethod
627 def generate_hex_geom(hexes_dict,
628 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]],
629 vol_tag=-1):
630 """
631 Generates hexahedra volumes from data in hexes dict
632 :param hexes_dict: dictionary of data generated by either Winding or FQPL class in Hexahedrons.py
633 :param cons_for_lines: line connection defined as list of list in terms of point numbers to connect in the hexahedron
634 :param vol_tag: volume tag to use for generated volume of hexahedron
635 :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).
636 """
637 tags_dict = {}
638 for h_num, h_dict in hexes_dict.hexes.items():
639 p_ts = [] # Point tags
640 channel_turn = h_dict.pop('ct', None)
641 for p_num, p_dict in h_dict.items():
642 p_ts.append(gmsh.model.occ.addPoint(p_dict['x'], p_dict['y'], p_dict['z']))
644 l_ts = [] # Line tags
645 for p_c in cons_for_lines: # point connections to make a line
646 l_ts.append(gmsh.model.occ.addLine(p_ts[p_c[0]], p_ts[p_c[1]]))
648 cl_ts = [] # Curved Loops tags
649 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
650 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]]]))
651 sf_ts = [] # Surface Filling tags
652 for cl_t in cl_ts:
653 sf_ts.append(gmsh.model.occ.addSurfaceFilling(cl_t, degree=3, tol2d=0.000001, tol3d=0.00001, tolAng=0.001, tolCurv=0.05))
654 sl_t = gmsh.model.occ.addSurfaceLoop(sf_ts, sewing=True)
655 v_t = gmsh.model.occ.addVolume([sl_t], vol_tag)
656 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}
657 return tags_dict
660class Prepare_BREPs:
661 def __init__(self, fdm, verbose=True) -> object:
662 """
663 Class to preparing brep files by adding terminals.
664 :param fdm: FiQuS data model
665 :param verbose: If True more information is printed in python console.
666 """
668 self.cctdm = fdm.magnet
669 self.model_folder = os.path.join(os.getcwd())
670 self.magnet_name = fdm.general.magnet_name
672 self.verbose = verbose
673 self.winding_info_file = os.path.join(self.model_folder, f'{self.magnet_name}.wi')
674 self.cctwi = uff.read_data_from_yaml(self.winding_info_file, WindingsInformation)
675 self.gu = GmshUtils(self.model_folder, self.verbose)
676 self.gu.initialize()
677 self.model_file = os.path.join(self.model_folder, f'{self.magnet_name}.brep')
679 @staticmethod
680 def get_pts_for_sts(surface_tags):
681 """
682 Get point tags for surface tags
683 :param surface_tags: list of surface tags to get point tags
684 :return: list of point tags belonging to the surfaces in the list
685 """
686 vol_line_tags = []
687 vol_line_tags_i = 0
688 vol_line_tags_idx = []
689 for surf_tag in surface_tags:
690 line_tags_new = gmsh.model.getAdjacencies(2, surf_tag)[1]
691 for line_tag in line_tags_new:
692 vol_line_tags_i += 1
693 if line_tag not in vol_line_tags:
694 vol_line_tags.append(line_tag)
695 vol_line_tags_idx.append(vol_line_tags_i)
696 point_tags = []
697 point_tags_i = 0
698 point_tags_idx = []
699 for line_tag in vol_line_tags:
700 point_tags_new = gmsh.model.getAdjacencies(1, line_tag)[1]
701 for point_tag in point_tags_new:
702 point_tags_i += 1
703 if point_tag not in point_tags:
704 point_tags.append(point_tag)
705 point_tags_idx.append(point_tags_i)
706 return point_tags, vol_line_tags_idx, point_tags_idx
708 def straighten_terminal(self, gui=False):
709 """
710 Extends winding geom_generators to the air region boundary by extending the geom_generators with 'terminals' geom_generators up to the air boundary surfaces
711 By default this extends along the z axis and makes the final surface normal to the z axis.
712 :return: None, saves breps with straighten terminals to disk
713 """
715 for i, name in enumerate(self.cctwi.windings.names):
716 if self.verbose:
717 print(f'Straightening terminals of {name} started')
718 start_time = timeit.default_timer()
719 gmsh.open(os.path.join(self.model_folder, f'{name}.brep'))
720 for terminal in [self.cctwi.windings.t_in, self.cctwi.windings.t_out]:
721 vol_tag = terminal.vol_st[i]
722 vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
723 surf_tag = terminal.surf_st[i]
724 if surf_tag not in vol_surf_tags:
725 raise ValueError(f'Surface tag of {surf_tag} given for volume in of {vol_tag} does not belong to the volume!')
726 # surf_point_tags = self.get_pts_for_sts([surf_tag]) # only 'powered' surface
727 surf_point_tags, surf_line_tags_idx, surf_point_tags_idx = self.get_pts_for_sts([surf_tag]) # only 'powered' surface
728 vol_point_tags, vol_line_tags_idx, vol_point_tags_idx = self.get_pts_for_sts(vol_surf_tags) # all tags of hex
729 vol_point_dict = {}
730 for p_tag in vol_point_tags:
731 xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
732 vol_point_dict[p_tag] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2}
733 z = []
734 y = []
735 x = []
736 for p_tag in surf_point_tags: # calculate average z position for the terminal surface
737 xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
738 x.append((xmin + xmax) / 2)
739 y.append((ymin + ymax) / 2)
740 z.append((zmin + zmax) / 2)
741 x_avg = np.mean(x)
742 y_avg = np.mean(y)
743 z_avg = np.mean(z)
744 sign_x = [math.copysign(1, x_i - x_avg) for x_i in x]
745 sign_y = [math.copysign(1, y_i - y_avg) for y_i in y]
746 dist_x = []
747 dist_y = []
748 for j in range(len(x) - 1):
749 dist_x.append(math.sqrt((x[j] - x[j + 1]) ** 2))
750 dist_y.append(math.sqrt((y[j] - y[j + 1]) ** 2))
751 eq_len = self.cctdm.geometry.windings.wwhs[i] / 2
752 y_shift_sign = math.copysign(1, -terminal.z_air * self.cctdm.geometry.windings.alphas[i])
753 new_x = [x_avg + s_x * eq_len for s_x in sign_x]
754 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]
755 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
756 vol_point_dict[p_tag]['x'] = x_n
757 vol_point_dict[p_tag]['y'] = y_n
758 vol_point_dict[p_tag]['z'] = z_avg
759 gmsh.model.occ.remove([(3, terminal.vol_st[i])], recursive=True)
760 gmsh.model.occ.synchronize()
761 hexes = Hexahedrons() # create hex class instance to be able to reuse existing code for generating hexes geom_generators
762 hexes.hexes[0] = {} # only one winding layer is involved per iteration, so hardcoded 0 index
763 for p_i, (_, p_dict) in enumerate(vol_point_dict.items()):
764 hexes.hexes[0][p_i] = p_dict
765 Generate_BREPs.generate_hex_geom(hexes, cons_for_lines=terminal.lc_st[i], vol_tag=terminal.vol_st[i])
766 gmsh.model.occ.synchronize()
767 gmsh.write(os.path.join(self.model_folder, f'{name}.brep'))
768 if self.verbose:
769 print(f'Straightening terminals of {name} took {timeit.default_timer() - start_time:.2f} s')
770 # uff.write_data_to_yaml(self.winding_info_yaml_path, self.cctvi.dict())
771 if gui:
772 gmsh.model.occ.synchronize()
773 self.gu.launch_interactive_GUI()
774 else:
775 gmsh.clear()
777 def extend_terms(self, operation='extend', gui=False, ):
778 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.
779 if operation == 'add':
780 for terminal in [self.cctwi.windings.t_in, self.cctwi.windings.t_out]:
781 terminal.ndpterms = [1] * len(terminal.ndpterms)
782 terminal.z_air = terminal.z_add
783 elif operation == 'extend':
784 pass
785 file_in_postfix = ''
786 file_out_postfix = ''
787 for i, name in enumerate(self.cctwi.windings.names):
788 if self.verbose:
789 print(f'Extending terminals of {name} started')
790 start_time = timeit.default_timer()
791 gmsh.open(os.path.join(self.model_folder, f'{name}{file_in_postfix}.brep'))
792 volumes = [vol[1] for vol in gmsh.model.getEntities(dim=3)]
793 for oper, terminal in enumerate([self.cctwi.windings.t_in, self.cctwi.windings.t_out]):
794 vol_tag = terminal.vol_et[i]
795 vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
796 surf_tag = terminal.surf_et[i]
797 if surf_tag not in vol_surf_tags:
798 raise ValueError(f'Surface tag of {surf_tag} given for volume in of {vol_tag} does not belong to the volume!')
799 surf_point_tags, surf_line_tags_idx, surf_point_tags_idx = self.get_pts_for_sts([surf_tag])
800 surf_point_tags = sorted(surf_point_tags)
801 vol_point_tags, vol_line_tags_idx, vol_point_tags_idx = self.get_pts_for_sts(vol_surf_tags)
802 vol_point_tags = sorted(vol_point_tags)
803 surf_point_tags_idx_pw = [i for i, e in enumerate(vol_point_tags) if e in set(surf_point_tags)] # powered
804 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)
805 vol_point_dict = {}
806 for idx_pw, p_tag in zip(surf_point_tags_idx_pw, surf_point_tags):
807 xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
808 vol_point_dict[idx_pw] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2}
809 z_ext = round((terminal.z_air - zmin) / terminal.ndpterms[i], 8)
810 for idx_oth, p_tag in zip(surf_point_tags_idx_oth, surf_point_tags):
811 xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
812 vol_point_dict[idx_oth] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2 + z_ext}
813 hexes = Hexahedrons() # create hex class instance to be able to reuse existing code for generating hexes geom_generators
814 for h in range(terminal.ndpterms[i]):
815 hexes.hexes[h] = {}
816 for p_i, (_, p_dict) in enumerate(vol_point_dict.items()):
817 hexes.hexes[h][p_i] = copy.deepcopy(p_dict)
818 for p_t, p_dict in vol_point_dict.items():
819 p_dict['z'] = p_dict['z'] + z_ext
820 for idx_oth in range(4, 8):
821 z_air = terminal.z_air
822 hexes.hexes[h][idx_oth]['z'] = z_air
823 tags_dict = Generate_BREPs.generate_hex_geom(hexes, cons_for_lines=terminal.lc_et[i], vol_tag=-1)
824 new_volumes = []
825 for _, hex_dict in tags_dict.items():
826 new_volumes.append(hex_dict['v_t'])
827 gmsh.model.occ.synchronize()
828 if oper == 0: # this vol_in is needed for reordering volumes later
829 vol_in = new_volumes
830 renumbered_vols = []
831 other_vols = list(set(volumes) - set(vol_in))
832 max_tag = gmsh.model.occ.getMaxTag(3)
833 for vol in other_vols:
834 gmsh.model.setTag(3, vol, vol + max_tag)
835 renumbered_vols.append(vol + max_tag)
836 for n_tag, vol in enumerate(reversed(vol_in)):
837 gmsh.model.setTag(3, vol, n_tag + 1)
838 max_term_tag = len(vol_in) + 1
839 spiral_volumes = []
840 for n_tag, vol in enumerate(renumbered_vols):
841 gmsh.model.setTag(3, vol, n_tag + max_term_tag)
842 spiral_volumes.append(n_tag + max_term_tag)
843 gmsh.write(os.path.join(self.model_folder, f'{name}{file_out_postfix}.brep'))
844 if self.verbose:
845 print(f'Straightening terminals of {name} took {timeit.default_timer() - start_time:.2f} s')
846 print(f'Writing volume information file: {name}.vi ')
847 vi = {'export': spiral_volumes, 'all': [vol[1] for vol in gmsh.model.getEntities(dim=3)]}
848 json.dump(vi, open(f"{os.path.join(self.model_folder, name)}.vi", 'w'))
849 if self.verbose:
850 print(f'Done writing volume information file: {name}.vi ')
851 if gui:
852 self.gu.launch_interactive_GUI()
853 else:
854 gmsh.clear()
856 def save_fqpl_vi(self):
857 for f_name in self.cctdm.geometry.fqpls.names:
858 if self.verbose:
859 print(f'Writing volume information file: {f_name}.vi ')
860 gmsh.open(os.path.join(self.model_folder, f'{f_name}.brep'))
861 volumes = [vol[1] for vol in gmsh.model.getEntities(dim=3)]
862 export = volumes
863 vi = {'export': export, 'all': volumes}
864 json.dump(vi, open(f"{os.path.join(self.model_folder, f_name)}.vi", 'w'))
865 if self.verbose:
866 print(f'Done writing volume information file: {f_name}.vi ')
867 gmsh.clear()
869 def fragment(self, gui=False):
870 if self.verbose:
871 print('Loading files in preparation for fragment operation')
872 for f_name in self.cctwi.w_names + self.cctwi.f_names:
873 gmsh.merge(os.path.join(self.model_folder, f'{f_name}.brep'))
874 num_vol = np.max([vol[1] for vol in gmsh.model.getEntities(dim=3)])
875 for f_name in self.cctwi.formers:
876 gmsh.merge(os.path.join(self.model_folder, f'{f_name}.brep'))
877 num_vol += 1
878 gmsh.merge(os.path.join(self.model_folder, f'{self.cctwi.air}.brep'))
879 num_vol += 1
880 entities = gmsh.model.getEntities(dim=3)
881 if len(entities) != num_vol:
882 raise ValueError('Not consistent volumes numbers in brep and json files!')
883 objectDimTags = [entities[-1]]
884 toolDimTags = entities[:-1]
885 # central_line = [(1, 3898)]
886 # toolDimTags = toolDimTags+central_line
887 if self.verbose:
888 print(f'Fragmenting {self.magnet_name} started')
889 start_time = timeit.default_timer()
890 gmsh.model.occ.fragment(objectDimTags, toolDimTags, removeObject=True, removeTool=True)
891 gmsh.model.occ.synchronize()
892 if self.verbose:
893 print(f'Fragmenting {self.magnet_name} took {timeit.default_timer() - start_time:.2f} s')
895 gmsh.write(self.model_file)
896 if gui:
897 self.gu.launch_interactive_GUI()
898 else:
899 gmsh.clear()
900 gmsh.finalize()
902 def load_geometry(self, gui=False):
903 gmsh.open(self.model_file)
904 if gui:
905 self.gu.launch_interactive_GUI()