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 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 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_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)
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