Source code for opstool.pre.io._read_gmsh

from __future__ import annotations

import re
from collections import defaultdict

import gmsh
import numpy as np

from ...utils import get_opensees_module

ops = get_opensees_module()

OPS_GMSH_ELE_TYPE = [1, 2, 3, 4, 5, 9, 10, 11, 12, 16, 17]


[docs] class Gmsh2OPS: """Generate OpenSees code from GMSH. Parameters ----------- ndm: int, default=3 Model dimension ndf: int, default=3 Number of degrees of freedom. """ def __init__(self, ndm: int = 3, ndf: int = 3): self.ndm = ndm self.ndf = ndf self.gmsh_entities = defaultdict(lambda: {}) self.gmsh_nodes = defaultdict(lambda: {}) self.gmsh_eles = defaultdict(lambda: {}) self.gmsh_physical_groups = defaultdict(list) self.gmsh_dim_entity_tags = None # key: name, value:[(dim, entity_tag)] self.all_node_tags, self.all_ele_tags = [], [] self.out_file = None self.out_type = None
[docs] def set_output_file(self, filename: str = "src.tcl", encoding: str = "utf-8"): """ Parameters: ------------ filename: str, default = "src.tcl". The output file-path-name must end with ``.tcl`` or ``.py``. encoding: str, default = "utf-8". The file encoding. """ self.out_file = filename if filename.endswith(".tcl"): self.out_type = "tcl" elif filename.endswith(".py"): self.out_type = "py" else: raise ValueError("output_filename must end with .tcl or .py!") # noqa: TRY003 with open(self.out_file, "w+", encoding=encoding) as outf: outf.write(f"# This file was created by {self.__class__.__name__}\n\n") if self.out_type == "py": outf.write("import openseespy.opensees as ops\n\n") outf.write("ops.wipe()\n") outf.write(f"ops.model('basic', '-ndm', {self.ndm}, '-ndf', {self.ndf})\n\n") else: outf.write("wipe\n") outf.write(f"model basic -ndm {self.ndm} -ndf {self.ndf}\n\n")
[docs] def read_gmsh_file(self, file_path: str, encoding: str = "utf-8", print_info: bool = True): """ Read an ``.msh`` file generated by ``GMSH``. .. Note:: You only need to use one of ``read_gmsh_file`` and ``read_gmsh_data``. ``read_gmsh_file`` is used to read data from the ``.msh`` file, and ``read_gmsh_data`` is used to read data from the runtime memory. Parameters ----------- file_path: str the file path. encoding: str, default='utf-8' The file encoding. print_info: bool, default=True Print info. """ with open(file_path, encoding=encoding) as inf: lines = [ln.strip() for ln in inf.readlines()] # Remove comments lines = [ln for ln in lines if not ln.startswith("**")] node_idx, ele_idx, entities_idx, physical_idx = [], [], [], [] node_idx.append(lines.index("$Nodes")) node_idx.append(lines.index("$EndNodes")) ele_idx.append(lines.index("$Elements")) ele_idx.append(lines.index("$EndElements")) entities_idx.append(lines.index("$Entities")) entities_idx.append(lines.index("$EndEntities")) if "$PhysicalNames" in lines: physical_idx.append(lines.index("$PhysicalNames")) physical_idx.append(lines.index("$EndPhysicalNames")) # key: (dim, physical_tag), value: physical_name physical_tag_name_map = _retrieve_physical_groups(lines, physical_idx) self.gmsh_entities, self.gmsh_physical_groups = _retrieve_entities(lines, entities_idx, physical_tag_name_map) self.gmsh_dim_entity_tags = list(self.gmsh_entities.keys()) self.gmsh_nodes, self.all_node_tags = _retrieve_nodes(lines, node_idx) self.gmsh_eles, self.all_ele_tags = _retrieve_eles(lines, ele_idx) if print_info: self._print_info()
[docs] def read_gmsh_data(self, print_info: bool = True): """ Read data from ``GMSH`` at runtime. .. Note:: * When using a command such as ``gmsh.finalize()`` to close gmsh, you need to use it after this command, otherwise the data cannot be read. * You only need to use one of ``read_gmsh_file`` and ``read_gmsh_data``. ``read_gmsh_file`` is used to read data from the ``.msh`` file, and ``read_gmsh_data`` is used to read data from the runtime memory. Parameters ----------- print_info: bool, default=True Print info. """ self.gmsh_dim_entity_tags = gmsh.model.getEntities() for dim, etag in self.gmsh_dim_entity_tags: bound_dimtags = gmsh.model.getBoundary(dimTags=[(dim, etag)], oriented=False) self.gmsh_entities[(dim, etag)]["BoundTags"] = [data[1] for data in bound_dimtags] # // nodeTags, nodeCoords, _ = gmsh.model.mesh.getNodes(dim, etag) nodeCoords = np.reshape(nodeCoords, (-1, 3)) for tag, coord in zip(nodeTags, nodeCoords): self.all_node_tags.append(int(tag)) self.gmsh_nodes[(dim, etag)][int(tag)] = list(coord) # // _, elemTags, _ = gmsh.model.mesh.getElements(dim, etag) ele_tags = [item for row in elemTags for item in row] for tag in ele_tags: ele_type, node_tags, dim, etag = gmsh.model.mesh.getElement(tag) node_tags = [int(i) for i in node_tags] node_tags = _reshape_ele_node_order(ele_type, node_tags) node_tags.append(ele_type) self.gmsh_eles[(dim, etag)][int(tag)] = node_tags self.all_ele_tags.append(int(tag)) vGroups = gmsh.model.getPhysicalGroups() for iGroup in vGroups: dimGroup = iGroup[0] # 1D, 2D or 3D tagGroup = iGroup[1] namGroup = gmsh.model.getPhysicalName(dimGroup, tagGroup) vEntities = gmsh.model.getEntitiesForPhysicalGroup(dimGroup, tagGroup) dim_entity_tags = [(dimGroup, etag) for etag in vEntities] self.gmsh_physical_groups[namGroup].extend(dim_entity_tags) if print_info: self._print_info()
def _print_info(self): num_points, num_curves, num_surf, num_vol, n = 0, 0, 0, 0, 0 for dim, _etag in self.gmsh_dim_entity_tags: if dim == 0: num_points += 1 elif dim == 1: num_curves += 1 elif dim == 2: num_surf += 1 elif dim == 3: num_vol += 1 n += 1 print( f"Info:: Geometry Information >>>\n" f"{n} Entities: {num_points} Point; " f"{num_curves} Curves; " f"{num_surf} Surfaces; " f"{num_vol} Volumes.\n" ) group_names = list(self.gmsh_physical_groups.keys()) print("Info:: Physical Groups Information >>>") print(f"{len(group_names)} Physical Groups.") print(f"Physical Group names: {group_names}\n") # -------------------------------------- print("Info:: Mesh Information >>>") print( f"{len(self.all_node_tags)} Nodes; MaxNodeTag {max(self.all_node_tags)}; " f"MinNodeTag {min(self.all_node_tags)}." ) print( f"{len(self.all_ele_tags)} Elements; MaxEleTag {max(self.all_ele_tags)}; " f"MinEleTag {min(self.all_ele_tags)}.\n" )
[docs] def set_node_element_tags_offset(self, node_offset: int | None = None, ele_offset: int | None = None): """ Set node and element tags offset. Parameters ----------- start_node_tag: int, default=None The starting node tag. If None, no offset will be applied. If not None, the new node tag = old node tag + node_offset. start_ele_tag: int, default=None The starting element tag. If None, no offset will be applied. If not None, the new element tag = old element tag + ele_offset. """ if node_offset is not None: new_gmsh_nodes = defaultdict(lambda: {}) for key, nodes in self.gmsh_nodes.items(): for tag, coords in nodes.items(): new_gmsh_nodes[key][tag + node_offset] = coords self.gmsh_nodes = new_gmsh_nodes self.all_node_tags = [tag + node_offset for tag in self.all_node_tags] for key, eles in self.gmsh_eles.items(): for tag, ntags in eles.items(): ntags = [nt + node_offset for nt in ntags] self.gmsh_eles[key][tag] = ntags if ele_offset is not None: new_gmsh_eles = defaultdict(lambda: {}) for key, eles in self.gmsh_eles.items(): for tag, ntags in eles.items(): new_gmsh_eles[key][tag + ele_offset] = ntags self.gmsh_eles = new_gmsh_eles self.all_ele_tags = [tag + ele_offset for tag in self.all_ele_tags]
[docs] def get_node_tags( self, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, ) -> list: """Return node tags in Gmsh, which will also be the same as in OpenSeesPy. Parameters ----------- dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of GMSH dimension and entity tags. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. Returns --------- node_tags: list A list containing node tags. """ if dim_entity_tags is None and physical_group_names is None: entity_tags = self.gmsh_nodes.keys() elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) node_tags = [] for key in entity_tags: for tag in self.gmsh_nodes[key]: node_tags.append(tag) return node_tags
[docs] def create_node_cmds( self, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, start_node_tag: int | None = None, ) -> list: """ Create ``OpenSeesPy`` nodes at runtime. .. note:: This function can be used multiple times, and the node tags will be automatically incremented based on the previous maximum node tag. Parameters ----------- dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of GMSH dimension and entity tags. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. start_node_tag: int, default=None The starting node tag. If None, the original GMSH node tags will be used. Else, the new node tags will start from start_node_tag and increment by 1. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. Returns --------- node_tags: list, a list containing `openseespy` node tags. """ if dim_entity_tags is None and physical_group_names is None: entity_tags = self.gmsh_nodes.keys() elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) # create nodes node_tags = [] if start_node_tag is None: for key in entity_tags: for tag, coords in self.gmsh_nodes[key].items(): ops.node(tag, *coords) node_tags.append(tag) else: current_node_tag = start_node_tag for key in entity_tags: for _tag, coords in self.gmsh_nodes[key].items(): ops.node(current_node_tag, *coords) node_tags.append(current_node_tag) current_node_tag += 1 return node_tags
[docs] def write_node_file( self, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, start_node_tag: int | None = None, ): """ Write a node commands file, Tcl or Python. Parameters ----------- dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of GMSH dimension and entity tags. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. start_node_tag: int, default=None The starting node tag. If None, the original GMSH node tags will be used. Else, the new node tags will start from start_node_tag and increment by 1. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. """ if dim_entity_tags is None and physical_group_names is None: entity_tags = self.gmsh_nodes.keys() elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) with open(self.out_file, "a+") as outf: outf.write("\n# Create node commands\n\n") node_tags = [] if start_node_tag is None: for key in entity_tags: for tag, coords in self.gmsh_nodes[key].items(): if self.out_type == "tcl": coords = " ".join(map(str, coords[: self.ndm])) outf.write(f"node {tag} {coords}\n") else: content = [ f'"{item}"' if isinstance(item, str) else str(item) for item in coords[: self.ndm] ] content = ", ".join(content) outf.write(f"ops.node({tag}, {content})\n") node_tags.append(tag) else: current_node_tag = start_node_tag for key in entity_tags: for _tag, coords in self.gmsh_nodes[key].items(): if self.out_type == "tcl": coords_ = " ".join(map(str, coords[: self.ndm])) outf.write(f"node {current_node_tag} {coords_}\n") else: content = [ f'"{item}"' if isinstance(item, str) else str(item) for item in coords[: self.ndm] ] content = ", ".join(content) outf.write(f"ops.node({current_node_tag}, {content})\n") node_tags.append(current_node_tag) current_node_tag += 1 return node_tags
[docs] def get_element_tags( self, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, ) -> list: """Return element tags in Gmsh, which will also be the same as in OpenSeesPy. Parameters ----------- dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of dimension and entity tag containing element information that will be converted to OpenSeesPy elements. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. Returns --------- ele_tags: list A list containing element tags. """ ele_tags = [] if dim_entity_tags is None and physical_group_names is None: for _, ele_nodes in self.gmsh_eles.items(): if ele_nodes: for tag in ele_nodes: ele_tags.append(tag) return ele_tags elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) for etag in entity_tags: etag = (int(etag[0]), int(etag[1])) for tag in self.gmsh_eles[etag]: ele_tags.append(tag) return ele_tags
[docs] def create_element_cmds( self, ops_ele_type: str, ops_ele_args: list | None = None, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, start_ele_tag: int | None = None, ) -> list: """Create ``OpenSeesPy`` elements at runtime. Parameters ----------- ops_ele_type: str the `OpenSeesPy` element type to generate. ops_ele_args: list, default None Parameters except `OpenSeesPy` element tag and connected node tags. If None, an empty list will be used. dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of dimension and entity tag containing element information that will be converted to OpenSeesPy elements. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. start_ele_tag: int, default=None The starting element tag. If None, the original GMSH element tags will be used. Else, the new element tags will start from start_ele_tag and increment by 1. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. Returns --------- ele_tags: list, a list containing `openseespy` element tags. """ if ops_ele_args is None: ops_ele_args = [] if dim_entity_tags is None and physical_group_names is None: entity_tags = self.gmsh_eles.keys() elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) # create elements ele_tags = [] if start_ele_tag is None: for etag in entity_tags: etag = (int(etag[0]), int(etag[1])) for tag, ntags in self.gmsh_eles[etag].items(): ops.element(ops_ele_type, tag, *ntags[:-1], *ops_ele_args) ele_tags.append(tag) else: current_ele_tag = start_ele_tag for etag in entity_tags: etag = (int(etag[0]), int(etag[1])) for _tag, ntags in self.gmsh_eles[etag].items(): ops.element(ops_ele_type, current_ele_tag, *ntags[:-1], *ops_ele_args) ele_tags.append(current_ele_tag) current_ele_tag += 1 return ele_tags
[docs] def write_element_file( self, ops_ele_type: str, ops_ele_args: list | None = None, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, start_ele_tag: int | None = None, ): """Write elements a command file, ``Tcl`` or ``Python``. Parameters ----------- ops_ele_type: str the `OpenSeesPy` element type to generate. ops_ele_args: list, default None Parameters except `OpenSeesPy` element tag and connected node tags. If None, an empty list will be used. dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of dimension and entity tag containing element information that will be converted to OpenSeesPy elements. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. start_ele_tag: int, default=None The starting element tag. If None, the original GMSH element tags will be used. Else, the new element tags will start from start_ele_tag and increment by 1. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. Returns --------- ele_tags: list, a list containing `openseespy` element tags. """ if ops_ele_args is None: ops_ele_args = [] if dim_entity_tags is None and physical_group_names is None: entity_tags = self.gmsh_eles.keys() elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) ele_tags = [] with open(self.out_file, "a+") as outf: outf.write(f"\n# Create element commands, type={ops_ele_type}\n\n") if start_ele_tag is None: for etag in entity_tags: etag = (int(etag[0]), int(etag[1])) for tag, ntags in self.gmsh_eles[etag].items(): if self.out_type == "tcl": nodetags = " ".join(map(str, ntags[:-1])) ele_args = " ".join(map(str, ops_ele_args)) outf.write(f"element {ops_ele_type} {tag} {nodetags} {ele_args}\n") else: content = [f'"{item}"' if isinstance(item, str) else str(item) for item in ops_ele_args] content = ", ".join(content) outf.write(f'ops.element("{ops_ele_type}", {tag}, *{ntags[:-1]}, {content})\n') ele_tags.append(tag) else: current_ele_tag = start_ele_tag for etag in entity_tags: etag = (int(etag[0]), int(etag[1])) for _tag, ntags in self.gmsh_eles[etag].items(): if self.out_type == "tcl": nodetags = " ".join(map(str, ntags[:-1])) ele_args = " ".join(map(str, ops_ele_args)) outf.write(f"element {ops_ele_type} {current_ele_tag} {nodetags} {ele_args}\n") else: content = [f'"{item}"' if isinstance(item, str) else str(item) for item in ops_ele_args] content = ", ".join(content) outf.write(f'ops.element("{ops_ele_type}", {current_ele_tag}, *{ntags[:-1]}, {content})\n') ele_tags.append(current_ele_tag) current_ele_tag += 1 return ele_tags
[docs] def create_fix_cmds( self, dofs: list, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, ) -> list: """ Create fix constraints for OpenSeesPy at runtime. Parameters ----------- dofs: list, degrees of freedom to be constrained. Forexample, [1, 1, 1] for 3D and 3Dof. dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of GMSH dimension and entity tags. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. Returns --------- node_tags: list, a list containing `openseespy` fixed node tags. """ if dim_entity_tags is None and physical_group_names is None: entity_tags = self.gmsh_dim_entity_tags elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) fixed_tags = [] for etag in entity_tags: etag = (int(etag[0]), int(etag[1])) for tag in self.gmsh_nodes[etag]: if tag not in fixed_tags: ops.fix(tag, *dofs) fixed_tags.append(tag) return fixed_tags
[docs] def write_fix_file( self, dofs: list, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, ): """ Write node fix commands file, Tcl or Python. Parameters ----------- dofs: list, degrees of freedom to be constrained. Forexample, [1, 1, 1] for 3D and 3Dof. dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of GMSH dimension and entity tags. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. """ if dim_entity_tags is None and physical_group_names is None: entity_tags = self.gmsh_dim_entity_tags elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) fixed_tags = [] with open(self.out_file, "a+") as outf: outf.write("\n# Create fix commands\n\n") for etag in entity_tags: etag = (int(etag[0]), int(etag[1])) for tag in self.gmsh_nodes[etag]: if tag not in fixed_tags: if self.out_type == "tcl": dofs_ = " ".join(map(str, dofs)) outf.write(f"fix {tag} {dofs_}\n") else: content = [f'"{item}"' if isinstance(item, str) else str(item) for item in dofs] content = ", ".join(content) outf.write(f"ops.fix({tag}, {content})\n") fixed_tags.append(tag)
[docs] def get_dim_entity_tags(self, dim: int | None = None) -> list: """ Get dim_entity_tags from GMSH. Parameters ---------- dim: int, optional, default None The dimension tag. If None, all entities will be returned. Returns ------- dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. """ if dim is None: return self.gmsh_dim_entity_tags else: dim_entity_tags = [] for dimi, etag in self.gmsh_dim_entity_tags: if dimi == dim: dim_entity_tags.append((dimi, etag)) return dim_entity_tags
[docs] def get_physical_groups(self) -> dict: """ Get the GMSH physical groups. Returns ------- gmsh_physical_groups: dict, the GMSH physical groups. dict[key=name, value=[(dim, entity_tag), ...]] """ # data = {value: key for key, value in self.gmsh_physical_groups.items()} return dict(self.gmsh_physical_groups)
[docs] def get_boundary_dim_tags( self, dim_entity_tags: list | tuple | None = None, physical_group_names: list | tuple | str | None = None, include_self: bool = False, ) -> list: """ Get all boundaries of the GMSH entities, including corner points. Parameters ---------- dim_entity_tags: list, the GMSH [(dim, entity tag), ...]. A list of GMSH dimension and entity tags. If None, `physical_group_names` will be used. physical_group_names: list, tuple, str, or None, default None. The physical group name or list of physical group names. If None, `dim_entity_tags` will be used. include_self: bool, default False If True, the output contains itself, which is dim_entity_tags. .. Note:: * If `dim_entity_tags` and `physical_group_names` are both None, all entities will be converted. * If `dim_entity_tags` and `physical_group_names` are both not None, `dim_entity_tags` will be used. Returns ------- Boundary dimension and entity tags list. """ if dim_entity_tags is None and physical_group_names is None: entity_tags = self.gmsh_dim_entity_tags elif dim_entity_tags is not None: entity_tags = np.atleast_2d(dim_entity_tags) entity_tags = [(int(etag[0]), int(etag[1])) for etag in entity_tags] else: if isinstance(physical_group_names, str): physical_group_names = [physical_group_names] entity_tags = [] for pname in physical_group_names: entity_tags.extend(self.gmsh_physical_groups[pname]) boundary_dimtags = [] if include_self: for dim, etag in entity_tags: boundary_dimtags.append((dim, etag)) _get_boundary_dim_tags(boundary_dimtags, entity_tags, self.gmsh_entities) return sorted(set(boundary_dimtags), key=lambda x: (x[0], x[1]))
def _get_boundary_dim_tags(boundary_dimtags, dim_entity_tags, entites): for dim_etag in dim_entity_tags: dim, etag = int(dim_etag[0]), int(dim_etag[1]) if dim > 0: bound_etags = entites[(dim, etag)]["BoundTags"] bound_dimtags = [(dim - 1, abs(data)) for data in bound_etags] boundary_dimtags.extend(bound_dimtags) _get_boundary_dim_tags(boundary_dimtags, bound_dimtags, entites) # def _get_boundary_dim_tags(boundary_dimtags, dim_entity_tags): # for etag in dim_entity_tags: # etag = [(int(etag[0]), int(etag[1]))] # bound_dimtags = gmsh.model.getBoundary(dimTags=etag, oriented=False) # boundary_dimtags.extend(bound_dimtags) # for eetag in bound_dimtags: # if eetag[0] > 0: # _get_boundary_dim_tags(boundary_dimtags, [eetag]) def _retrieve_physical_groups(lines, physical_idx): pattern = r'(\d+)\s+(\d+)\s+"(.*)"' tag_name_map = {} if len(physical_idx) > 0: idx = physical_idx[0] + 1 num = int(lines[idx]) print(f"Info:: {num} Physical Names.") for _i in range(num): idx += 1 match = re.match(pattern, lines[idx]) if match: dim = int(match.group(1)) tag = int(match.group(2)) name = match.group(3) else: raise RuntimeError("Not all physical groups have names set!") # noqa: TRY003 tag_name_map[(dim, tag)] = name return tag_name_map def _check_physical_tag_name_map(key, physical_tag_name_map): if key not in physical_tag_name_map: raise KeyError(f"(dim={key[0]}, physical tag={key[1]}) has no physical name set!") # noqa: TRY003 def _retrieve_entities(lines, entities_idx, physical_tag_name_map): entities = defaultdict(dict) gmsh_physical_groups = defaultdict(list) idx = entities_idx[0] + 1 num_point, num_curve, num_surf, num_vol = map(int, lines[idx].split()) idx += 1 idx = _parse_entities_block(lines, idx, 0, num_point, physical_tag_name_map, entities, gmsh_physical_groups) idx = _parse_entities_block(lines, idx, 1, num_curve, physical_tag_name_map, entities, gmsh_physical_groups) idx = _parse_entities_block(lines, idx, 2, num_surf, physical_tag_name_map, entities, gmsh_physical_groups) idx = _parse_entities_block(lines, idx, 3, num_vol, physical_tag_name_map, entities, gmsh_physical_groups) return entities, gmsh_physical_groups def _parse_entities_block(lines, idx, dim, num, physical_tag_name_map, entities, gmsh_physical_groups): for _ in range(num): parts = lines[idx].split() tag = int(parts[0]) if dim == 0: entities[(dim, tag)]["Coord"] = list(map(float, parts[1:4])) offset = 4 else: entities[(dim, tag)]["CoordBoundary"] = list(map(float, parts[1:7])) offset = 7 num_tags = int(parts[offset]) physical_tags = list(map(int, parts[offset + 1 : offset + 1 + num_tags])) for ptag in physical_tags: _check_physical_tag_name_map((dim, ptag), physical_tag_name_map) pname = physical_tag_name_map[(dim, ptag)] gmsh_physical_groups[pname].append((dim, tag)) entities[(dim, tag)]["physicalTags"] = physical_tags entities[(dim, tag)]["numPhysicalTags"] = num_tags if dim > 0: num_bound = int(parts[offset + 1 + num_tags]) bound_start = offset + 2 + num_tags entities[(dim, tag)]["numBound"] = num_bound entities[(dim, tag)]["BoundTags"] = list(map(int, parts[bound_start : bound_start + num_bound])) else: entities[(dim, tag)]["numBound"] = 0 entities[(dim, tag)]["BoundTags"] = [] idx += 1 return idx def _retrieve_nodes(lines, node_idx): all_node_tags = [] nodes = defaultdict(dict) idx = node_idx[0] + 1 contents = [int(data) for data in lines[idx].split(" ")] _, num_nodes, min_node_tag, max_node_tag = contents print(f"Info:: {num_nodes} Nodes; MaxNodeTag {max_node_tag}; MinNodeTag {min_node_tag}.") idx = node_idx[0] + 2 while idx < node_idx[1]: contents = [int(data) for data in lines[idx].split(" ")] dim, etag, parametric, num_nodes_inblock = contents for i in range(num_nodes_inblock): tag = int(lines[idx + i + 1]) coords = [float(data) for data in lines[idx + num_nodes_inblock + i + 1].split(" ")] nodes[(dim, etag)][tag] = coords all_node_tags.append(tag) idx += 2 * num_nodes_inblock + 1 return nodes, all_node_tags def _retrieve_eles(lines, ele_idx): all_ele_tags = [] eles = defaultdict(dict) idx = ele_idx[0] + 1 contents = [int(data) for data in lines[idx].split(" ")] _, num_eles, min_ele_tag, max_ele_tag = contents print(f"Info:: {num_eles} Elements; MaxEleTag {max_ele_tag}; MinEleTag {min_ele_tag}.") idx = ele_idx[0] + 2 while idx < ele_idx[1]: contents = [int(data) for data in lines[idx].split(" ")] dim, etag, ele_type, num_eles_inblock = contents if ele_type in OPS_GMSH_ELE_TYPE: for i in range(num_eles_inblock): info = [int(data) for data in lines[idx + i + 1].split(" ")] tag = info[0] node_tags = _reshape_ele_node_order(ele_type, info[1:]) node_tags += [ele_type] eles[(dim, etag)][tag] = node_tags all_ele_tags.append(tag) idx += num_eles_inblock + 1 return eles, all_ele_tags def _reshape_ele_node_order(ele_type, node_tags): if ele_type == 11: tags = _reshape_tet_n10(node_tags) elif ele_type == 17: tags = _reshape_hex_n20(node_tags) elif ele_type == 12: tags = _reshape_hex_n27(node_tags) else: tags = node_tags return tags def _reshape_tet_n10(node_tags): tags = [ node_tags[0], node_tags[1], node_tags[2], node_tags[3], node_tags[4], node_tags[5], node_tags[6], node_tags[7], node_tags[9], node_tags[8], ] return tags def _reshape_hex_n20(node_tags): tags = [ node_tags[0], node_tags[1], node_tags[2], node_tags[3], # ----------- node_tags[4], node_tags[5], node_tags[6], node_tags[7], # ----------- node_tags[8], node_tags[11], node_tags[13], node_tags[9], # ----------- node_tags[16], node_tags[18], node_tags[19], node_tags[17], # ----------- node_tags[10], node_tags[12], node_tags[14], node_tags[15], ] return tags def _reshape_hex_n27(node_tags): tags = [ node_tags[0], node_tags[1], node_tags[2], node_tags[3], # ----------- node_tags[4], node_tags[5], node_tags[6], node_tags[7], # ----------- node_tags[8], node_tags[11], node_tags[13], node_tags[9], # ----------- node_tags[16], node_tags[18], node_tags[19], node_tags[17], # ----------- node_tags[10], node_tags[12], node_tags[14], node_tags[15], # ----------- node_tags[20], node_tags[21], node_tags[22], node_tags[23], node_tags[24], node_tags[25], node_tags[26], ] return tags