diff --git a/src/cubitpy/cubit_to_fourc_input.py b/src/cubitpy/cubit_to_fourc_input.py index 5196644..9e7d7cf 100644 --- a/src/cubitpy/cubit_to_fourc_input.py +++ b/src/cubitpy/cubit_to_fourc_input.py @@ -30,7 +30,9 @@ from cubitpy.conf import cupy -def add_node_sets(cubit, exo, input_file, write_topology_information=True): +def add_node_sets( + cubit, exo, input_file, write_topology_information=True, use_exo_ids=False +): """Add the node sets contained in the cubit session/exo file to the yaml file.""" @@ -38,14 +40,20 @@ def add_node_sets(cubit, exo, input_file, write_topology_information=True): if len(cubit.node_sets) == 0: return - # Get names of the node sets - names = [] - for string_list in exo.variables["ns_names"]: - string = "" - for char in string_list: + # Get a mapping between the node set IDs and the node set names and keys in the exo file. + node_set_id_to_exo_name = {} + node_set_keys = [key for key in exo.variables.keys() if "node_ns" in key] + for i in range(len(exo.variables["ns_prop1"])): + node_set_id = int(exo.variables["ns_prop1"][i]) + node_set_key = node_set_keys[i] + node_set_name = "" + for char in exo.variables["ns_names"][i]: if isinstance(char, np.bytes_): - string += char.decode("UTF-8") - names.append(string) + node_set_name += char.decode("UTF-8") + node_set_id_to_exo_name[node_set_id] = { + "key": node_set_key, + "name": node_set_name, + } # Sort the sets into their geometry type node_sets = { @@ -54,20 +62,18 @@ def add_node_sets(cubit, exo, input_file, write_topology_information=True): cupy.geometry.surface: [], cupy.geometry.volume: [], } - boundary_condition_map = {} - node_set_keys = [key for key in exo.variables.keys() if "node_ns" in key] - for i_set, key in enumerate(node_set_keys): - bc_section, bc_description, geometry_type = cubit.node_sets[i_set] - node_sets[geometry_type].append(exo.variables[key][:]) - bc_key = (bc_section, geometry_type) - if bc_key not in boundary_condition_map.keys(): - boundary_condition_map[bc_key] = [] - boundary_condition_map[bc_key].append( - [len(node_sets[geometry_type]), bc_description, names[i_set]] - ) + for node_set_id, node_set_data in cubit.node_sets.items(): + bc_section, bc_description, geometry_type = node_set_data + node_set_key = node_set_id_to_exo_name[node_set_id]["key"] + node_sets[geometry_type].append(exo.variables[node_set_key][:]) + + if use_exo_ids: + bc_description["E"] = node_set_id + else: + bc_description["E"] = len(node_sets[geometry_type]) + if bc_section not in input_file.inlined.keys(): input_file[bc_section] = [] - bc_description["E"] = len(node_sets[geometry_type]) if not write_topology_information: # when working with external .exo meshes, we do not write the @@ -122,12 +128,9 @@ def add_exodus_geometry_section(cubit, input_file, rel_exo_file_path): The relative path (as seen from the yaml input file) to the exodus file that contains the mesh. """ - # Retrieve a list of the block IDs and the corresponding block data of the current session - element_block_ids = cubit.cubit.get_block_id_list() - element_blocks = cubit.blocks # Iterate over all blocks and add them to the input file - for cur_block_id, cur_block_data in zip(element_block_ids, element_blocks): + for cur_block_id, cur_block_data in cubit.blocks.items(): # retrieve the name of the geometry section that this block belongs to cur_geometry_section_key = cur_block_data[0].get_four_c_section() + " GEOMETRY" # If the geometry section for this block does not exist yet, create it @@ -238,8 +241,9 @@ def get_input_file_with_mesh(cubit): connectivity_keys = [key for key in exo.variables.keys() if "connect" in key] connectivity_keys.sort() i_element = 0 - for i_block, key in enumerate(connectivity_keys): - ele_type, block_dict = cubit.blocks[i_block] + for key in connectivity_keys: + key_id = int(key[7:]) + ele_type, block_dict = cubit.blocks[key_id] block_section = f"{ele_type.get_four_c_section()} ELEMENTS" if block_section not in input_file.sections.keys(): input_file[block_section] = [] diff --git a/src/cubitpy/cubitpy.py b/src/cubitpy/cubitpy.py index 602a261..d67b07a 100644 --- a/src/cubitpy/cubitpy.py +++ b/src/cubitpy/cubitpy.py @@ -39,6 +39,26 @@ from cubitpy.cubit_wrapper.cubit_wrapper_host import CubitConnect +def _get_and_check_ids(name, container, id_list, given_id): + """Perform checks for the block and node set IDs used in CubitPy.""" + + # Check that the IDs stored in container are the same as created with this function. + if not set(container.keys()) == set(id_list): + raise ValueError( + f"The existing {name} ids in CubitPy ({set(container.keys())}) don't match the ones in Cubit ({id_list})" + ) + + # Get the id of the block to create. + if given_id is None: + if len(id_list) > 0: + given_id = max(id_list) + 1 + else: + given_id = 1 + elif given_id in id_list: + raise ValueError(f"The provided {name} id {given_id} already exists {id_list}") + return given_id + + class CubitPy(object): """A wrapper class with additional functionality for cubit.""" @@ -69,12 +89,10 @@ def __init__(self, *, cubit_exe=None, **kwargs): # Set lists and counters for blocks and sets self._default_cubit_variables() - self.fourc_input = FourCInput() - def _default_cubit_variables(self): """Set the default values for the lists and counters used in cubit.""" - self.blocks = [] - self.node_sets = [] + self.blocks = {} + self.node_sets = {} self.fourc_input = FourCInput() def __getattr__(self, key, *args, **kwargs): @@ -128,7 +146,14 @@ def _name_created_set(self, set_type, set_id, name, item): self.cubit.cmd('{} {} name "{}"'.format(set_type, set_id, rename_name)) def add_element_type( - self, item, el_type, *, name=None, material=None, bc_description=None + self, + item, + el_type, + *, + name=None, + material=None, + bc_description=None, + block_id: int | None = None, ): """Add a block to cubit that contains the geometry in item. Also set the element type of block. @@ -147,6 +172,9 @@ def add_element_type( bc_description: dict Will be written after the material string. If this is not set, the default values for the given element type will be used. + block_id: + Optionally the block ID can be given by the user. If this ID already exists + an error will be raised. """ # default values @@ -155,19 +183,15 @@ def add_element_type( if bc_description is None: bc_description = {} - # Check that all blocks in cubit are created with this function. - n_blocks = len(self.blocks) - if not len(self.cubit.get_block_id_list()) == n_blocks: - raise ValueError( - "The block counter is {1}, but the number of blocks in cubit is {0}, all blocks should be created with this function!".format( - len(self.cubit.get_block_id_list()), n_blocks - ) - ) + # Check and get the block id for the new block. + block_id = _get_and_check_ids( + "block", self.blocks, self.cubit.get_block_id_list(), block_id + ) # Get element type of item. geometry_type = item.get_geometry_type() - self.cubit.cmd("create block {}".format(n_blocks + 1)) + self.cubit.cmd("create block {}".format(block_id)) if not isinstance(item, CubitGroup): cubit_scheme, cubit_element_type = el_type.get_cubit_names() @@ -181,30 +205,30 @@ def add_element_type( self.cubit.cmd( "block {} {} {}".format( - n_blocks + 1, geometry_type.get_cubit_string(), item.id() + block_id, geometry_type.get_cubit_string(), item.id() ) ) self.cubit.cmd( - "block {} element type {}".format(n_blocks + 1, cubit_element_type) + "block {} element type {}".format(block_id, cubit_element_type) ) else: - item.add_to_block(n_blocks + 1, el_type) + item.add_to_block(block_id, el_type) - self._name_created_set("block", n_blocks + 1, name, item) + self._name_created_set("block", block_id, name, item) # If the user does not give a bc_description, load the default one. if not bc_description: bc_description = el_type.get_default_four_c_description() # Add data that will be written to bc file. - self.blocks.append([el_type, material | bc_description]) + self.blocks[block_id] = [el_type, material | bc_description] def reset_blocks(self): """This method deletes all blocks in Cubit and resets the counter in this object.""" # Reset the block list of this object. - self.blocks = [] + self.blocks = {} # Delete all blocks. for block_id in self.get_block_id_list(): @@ -219,6 +243,7 @@ def add_node_set( bc_description=None, bc_section=None, geometry_type=None, + node_set_id: int | None = None, ): """Add a node set to cubit. This node set can have a boundary condition. @@ -239,34 +264,33 @@ def add_node_set( geometry_type: cupy.geometry Directly set the geometry type, instead of obtaining it from the given item. + node_set_id: + Optionally the node set ID can be given by the user. If this ID + already exists an error will be raised. """ - # Check that all node sets in cubit are created with this function. - n_node_sets = len(self.node_sets) - if not len(self.cubit.get_nodeset_id_list()) == n_node_sets: - raise ValueError( - "The node set counter is {1}, but the number of node sets in cubit is {0}, all node sets should be created with this function!".format( - len(self.cubit.get_nodeset_id_list()), n_node_sets - ) - ) + # Check and get the node set id for the new node set. + node_set_id = _get_and_check_ids( + "nodeset", self.node_sets, self.cubit.get_nodeset_id_list(), node_set_id + ) # Get element type of item if it was not explicitly given. if geometry_type is None: geometry_type = item.get_geometry_type() - self.cubit.cmd("create nodeset {}".format(n_node_sets + 1)) + self.cubit.cmd("create nodeset {}".format(node_set_id)) if not isinstance(item, CubitGroup): # Add the geometries to the node set in cubit. self.cubit.cmd( "nodeset {} {} {}".format( - n_node_sets + 1, geometry_type.get_cubit_string(), item.id() + node_set_id, geometry_type.get_cubit_string(), item.id() ) ) else: # Add the group to the node set in cubit. - item.add_to_nodeset(n_node_sets + 1) + item.add_to_nodeset(node_set_id) - self._name_created_set("nodeset", n_node_sets + 1, name, item) + self._name_created_set("nodeset", node_set_id, name, item) # Add data that will be written to bc file. if ( @@ -282,7 +306,7 @@ def add_node_set( bc_section = bc_type.get_dat_bc_section_header(geometry_type) if bc_description is None: bc_description = {} - self.node_sets.append([bc_section, bc_description, geometry_type]) + self.node_sets[node_set_id] = [bc_section, bc_description, geometry_type] def get_ids(self, geometry_type): """Get a list with all available ids of a certain geometry type.""" @@ -367,7 +391,13 @@ def dump(self, yaml_path, mesh_in_exo=False): # create a deep copy of the input_file input_file = self.fourc_input.copy() # Add the node sets - add_node_sets(self, exo, input_file, write_topology_information=False) + add_node_sets( + self, + exo, + input_file, + write_topology_information=False, + use_exo_ids=True, + ) # Add the problem geometry section rel_exo_path = os.path.relpath(exo_path, start=yaml_dir) add_exodus_geometry_section(self, input_file, rel_exo_path) diff --git a/tests/input-files-ref/test_yaml_with_exo_export.4C.yaml b/tests/input-files-ref/test_yaml_with_exo_export.4C.yaml index acc82de..a591b72 100644 --- a/tests/input-files-ref/test_yaml_with_exo_export.4C.yaml +++ b/tests/input-files-ref/test_yaml_with_exo_export.4C.yaml @@ -25,7 +25,7 @@ DESIGN SURF DIRICH CONDITIONS: - null - null - null - E: 3 + E: 17 ENTITY_TYPE: "node_set_id" - NUMDOF: 3 ONOFF: @@ -40,7 +40,53 @@ DESIGN SURF DIRICH CONDITIONS: - 1 - null - null - E: 4 + E: 18 + ENTITY_TYPE: "node_set_id" +DESIGN LINE NEUMANN CONDITIONS: + - NUMDOF: 3 + ONOFF: + - 1 + - 0 + - 0 + VAL: + - 1.0 + - 0.0 + - 0.0 + FUNCT: + - null + - null + - null + E: 19 + ENTITY_TYPE: "node_set_id" + - NUMDOF: 3 + ONOFF: + - 1 + - 0 + - 0 + VAL: + - 1.0 + - 0.0 + - 0.0 + FUNCT: + - null + - null + - null + E: 20 + ENTITY_TYPE: "node_set_id" + - NUMDOF: 3 + ONOFF: + - 1 + - 1 + - 0 + VAL: + - 1.0 + - 0.0 + - 0.0 + FUNCT: + - null + - null + - null + E: 15 ENTITY_TYPE: "node_set_id" STRUCTURE GEOMETRY: FILE: "test_yaml_with_exo_export.exo" @@ -49,6 +95,6 @@ STRUCTURE GEOMETRY: - ID: 1 ELEMENT_NAME: "SOLID" ELEMENT_DATA: "MAT 1 KINEM nonlinear" - - ID: 2 + - ID: 27 ELEMENT_NAME: "SOLID" ELEMENT_DATA: "MAT 2 KINEM nonlinear" diff --git a/tests/test_cubitpy.py b/tests/test_cubitpy.py index 50e577b..cfe0dc3 100644 --- a/tests/test_cubitpy.py +++ b/tests/test_cubitpy.py @@ -1824,6 +1824,7 @@ def test_yaml_with_exo_export(): "VAL": [0, 0, 0], "FUNCT": [None, None, None], }, + node_set_id=17, ) cubit.add_node_set( cubit.group(add_value="add surface 12"), @@ -1837,6 +1838,41 @@ def test_yaml_with_exo_export(): }, ) + cubit.add_node_set( + cubit.group(add_value="add curve 1"), + name="curve_1", + bc_type=cupy.bc_type.neumann, + bc_description={ + "NUMDOF": 3, + "ONOFF": [1, 0, 0], + "VAL": [1.0, 0.0, 0.0], + "FUNCT": [None, None, None], + }, + ) + cubit.add_node_set( + cubit.group(add_value="add curve 2"), + name="curve_2", + bc_type=cupy.bc_type.neumann, + bc_description={ + "NUMDOF": 3, + "ONOFF": [1, 0, 0], + "VAL": [1.0, 0.0, 0.0], + "FUNCT": [None, None, None], + }, + ) + cubit.add_node_set( + cubit.group(add_value="add curve 3"), + name="curve_3", + bc_type=cupy.bc_type.neumann, + bc_description={ + "NUMDOF": 3, + "ONOFF": [1, 1, 0], + "VAL": [1.0, 0.0, 0.0], + "FUNCT": [None, None, None], + }, + node_set_id=15, + ) + # Add the element types cubit.add_element_type( cubit.group(add_value="add volume 1"), @@ -1857,6 +1893,7 @@ def test_yaml_with_exo_export(): bc_description={ "KINEM": "nonlinear", }, + block_id=27, ) cubit.fourc_input.combine_sections( @@ -1897,10 +1934,10 @@ def test_yaml_with_exo_export_fsi(): # Create Bottom cubit.cmd(f"brick x {Width} y {BottomHeight} z {Depth}") - cubit.cmd(f"volume 1 move x {Width/2} y {-BottomHeight/2} z {-Depth/2}") + cubit.cmd(f"volume 1 move x {Width / 2} y {-BottomHeight / 2} z {-Depth / 2}") # Create Fluid Part - cubit.cmd(f"brick x {Width} y {CavityHeight+InflowHeight} z {Depth}") + cubit.cmd(f"brick x {Width} y {CavityHeight + InflowHeight} z {Depth}") cubit.cmd("align volume 2 surface 9 with surface 5") # $ divide cavity and inflow region cubit.cmd(f"webcut volume 2 with plane yplane offset {CavityHeight} imprint merge")