Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Homogenizing geometry API #2125

Merged
merged 51 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
9e63059
Changing api to return pv.multiblocks
germa89 May 31, 2023
5b385b8
Adding tests
germa89 May 31, 2023
26b6fcd
Adding typing
germa89 Jun 16, 2023
6b28e2e
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jun 19, 2023
9e457c7
Apply suggestions from code review
germa89 Jun 20, 2023
15c5300
Apply suggestions from code review
germa89 Jun 20, 2023
0a1c52b
More statically typing
germa89 Jun 23, 2023
6ad7f0c
Removing unused function
germa89 Jun 23, 2023
8824cec
More static typing
germa89 Jun 23, 2023
e7a7924
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jul 3, 2023
d6d7163
Fixing tests
germa89 Jul 3, 2023
b2395a7
Using `get_lines` to get old behavior
germa89 Jul 3, 2023
2b5a6c4
Updating docstring
germa89 Jul 3, 2023
608f755
spliting tests
germa89 Jul 3, 2023
ebb0fe3
correcting example
germa89 Jul 3, 2023
ad1f300
fix missing variable
germa89 Jul 3, 2023
4c18af3
fixing download tests
germa89 Jul 4, 2023
2437d99
Fixing build
germa89 Jul 4, 2023
6c34387
Fixing method get_volume.
germa89 Jul 4, 2023
402be9e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 4, 2023
c98b681
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jul 4, 2023
b39d8dd
fix tests
germa89 Jul 5, 2023
7cb762b
Adding missing import for type checking
germa89 Jul 5, 2023
1309771
avoiding component exception
germa89 Jul 5, 2023
1c0b4b7
Adding API tests
germa89 Jul 5, 2023
f5b491e
Adding/improving doc strings
germa89 Jul 6, 2023
d3ad10b
Adding access to block through names
germa89 Jul 6, 2023
a9d4d14
Adding return_ids_in_array to get_keypoints
germa89 Jul 6, 2023
fbdbb18
Adding setitem and getitem methods.
germa89 Jul 6, 2023
e68b5f6
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jul 6, 2023
a46b41c
Adding custom build in Makefile to avoid running examples
germa89 Jul 6, 2023
e517730
Adding legacy mode
germa89 Jul 6, 2023
7232430
Adding documentation
germa89 Jul 7, 2023
828d524
Fixing spelling.
germa89 Jul 7, 2023
0c656c9
Fixing vale
germa89 Jul 7, 2023
3727c5d
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jul 7, 2023
61b603f
Adding trame
germa89 Jul 7, 2023
071dbdb
Apply suggestions from code review
germa89 Jul 7, 2023
52508b3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 7, 2023
28618b3
Implementing review changes
germa89 Jul 7, 2023
57f80dd
adding debug levels
germa89 Jul 7, 2023
d0d3a99
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jul 14, 2023
1eb1822
Apply suggestions from code review
germa89 Jul 18, 2023
faeb267
Apply suggestions from code review
germa89 Jul 18, 2023
9f92390
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 18, 2023
8633d69
Apply suggestions from code review
germa89 Jul 18, 2023
15e0994
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jul 18, 2023
a8583ae
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jul 18, 2023
b5b1d29
fixing typo
germa89 Jul 18, 2023
54161c7
Apply suggestions from code review
germa89 Jul 21, 2023
32c5b00
Merge branch 'main' into feat/homogenizing-geometry-API
germa89 Jul 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 103 additions & 37 deletions src/ansys/mapdl/core/mapdl_geometry.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Module to support MAPDL CAD geometry"""
import re
from typing import List, Optional, Sequence, Union

import numpy as np
from numpy.typing import NDArray

from ansys.mapdl.core import _HAS_PYVISTA

Expand All @@ -28,7 +30,7 @@
}


def merge_polydata(items):
def merge_polydata(items: Union[pv.PolyData, pv.UnstructuredGrid]):
"""Merge list of polydata or unstructured grids"""

# lazy import here for faster module loading
Expand All @@ -45,7 +47,7 @@ def merge_polydata(items):
return pv.wrap(afilter.GetOutput())


def get_elements_per_area(resp):
def get_elements_per_area(resp: str) -> List[int]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Get the number of elements meshed for each area given the response
from ``AMESH``.

Expand Down Expand Up @@ -106,10 +108,10 @@ def __init__(self, mapdl):
self._lines_cache = None
self._log = self._mapdl._log

def _set_log_level(self, level):
def _set_log_level(self, level: str) -> None:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
return self._mapdl.set_log_level(level)

def _load_iges(self):
def _load_iges(self) -> "Iges":
"""Loads the iges file from MAPDL as a pyiges class"""
# Lazy import here for speed and stability
# possible to exclude this import in the future
Expand All @@ -121,35 +123,51 @@ def _load_iges(self):
)
return Iges(self._mapdl._generate_iges())

def _reset_cache(self):
def _reset_cache(self) -> None:
self._keypoints_cache = None
self._lines_cache = None

@property
def _keypoints(self):
def _keypoints(self) -> pv.PolyData:
"""Returns keypoints cache"""
if self._keypoints_cache is None:
self._keypoints_cache = self._load_keypoints()
return self._keypoints_cache

@property
def keypoints(self):
"""Keypoint coordinates"""
def keypoints(self) -> pv.MultiBlock:
"""Keypoint entities as Pyvista MultiBlock"""
points = pv.MultiBlock()
for each_kp in self._keypoints.points:
points.append(pv.PointSet(each_kp))
return points

def get_keypoints(self) -> NDArray:
"""Keypoint coordinates entities as a Numpy array"""
return np.asarray(self._keypoints.points)

@property
def _lines(self):
def _lines(self) -> pv.MultiBlock:
"""Returns lines cache"""
germa89 marked this conversation as resolved.
Show resolved Hide resolved
if self._lines_cache is None:
self._lines_cache = self._load_lines()
return self._lines_cache

@property
def lines(self):
"""Active lines as a pyvista.PolyData"""
def lines(self) -> pv.MultiBlock:
"""Active lines as a pyvista.MultiBlock"""
return self._lines

def areas(self, quality=4, merge=False):
def get_lines(self) -> pv.MultiBlock:
"""Active lines as a pyvista.MultiBlock"""
return self.lines

@property
def areas(self) -> pv.MultiBlock:
"""List of areas from MAPDL represented as ``pyvista.MultiBlock"""
return self.get_areas()

def get_areas(self, quality: int = 4, merge: bool = False) -> pv.MultiBlock:
"""List of areas from MAPDL represented as ``pyvista.PolyData``.

Parameters
Expand All @@ -176,7 +194,7 @@ def areas(self, quality=4, merge=False):
Return a list of areas as indiviudal grids

>>> areas = mapdl.areas(quality=3)
>>> areab
>>> areas
[UnstructuredGrid (0x7f14add95040)
N Cells: 12
N Points: 20
Expand Down Expand Up @@ -215,7 +233,8 @@ def areas(self, quality=4, merge=False):
return surf

entity_num = surf["entity_num"]
areas = []

areas = pv.MultiBlock()
anums = np.unique(entity_num)
for anum in anums:
areas.append(surf.extract_cells(entity_num == anum))
Expand All @@ -224,7 +243,13 @@ def areas(self, quality=4, merge=False):

@supress_logging
@run_as_prep7
def generate_surface(self, density=4, amin=None, amax=None, ninc=None):
def generate_surface(
self,
density: int = 4,
amin: Optional[int] = None,
amax: Optional[int] = None,
ninc: Optional[int] = None,
) -> pv.PolyData:
"""
Generate an all-triangular surface of the active surfaces.

Expand Down Expand Up @@ -329,7 +354,7 @@ def generate_surface(self, density=4, amin=None, amax=None, ninc=None):
return pd

@property
def n_volu(self):
def n_volu(self) -> int:
"""
Number of volumes currently selected.

Expand All @@ -341,7 +366,7 @@ def n_volu(self):
return self._item_count("VOLU")

@property
def n_area(self):
def n_area(self) -> int:
"""
Number of areas currently selected.

Expand All @@ -353,7 +378,7 @@ def n_area(self):
return self._item_count("AREA")

@property
def n_line(self):
def n_line(self) -> int:
"""
Number of lines currently selected.

Expand All @@ -365,7 +390,7 @@ def n_line(self):
return self._item_count("LINE")

@property
def n_keypoint(self):
def n_keypoint(self) -> int:
"""
Number of keypoints currently selected.

Expand All @@ -377,12 +402,12 @@ def n_keypoint(self):
return self._item_count("KP")

@supress_logging
def _item_count(self, entity):
def _item_count(self, entity: str) -> int:
"""Return item count for a given entity."""
return int(self._mapdl.get(entity=entity, item1="COUNT"))

@property
def knum(self):
def knum(self) -> NDArray[np.integer]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""
Array of keypoint numbers.

Expand All @@ -398,7 +423,7 @@ def knum(self):
return self._mapdl.get_array("KP", item1="KLIST").astype(np.int32)

@property
def lnum(self):
def lnum(self) -> "ndarray[np.integer]":
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Array of line numbers.

Examples
Expand All @@ -419,7 +444,7 @@ def lnum(self):
return lnum.astype(np.int32)

@property
def anum(self):
def anum(self) -> NDArray[np.integer]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Array of area numbers.

Examples
Expand All @@ -435,7 +460,7 @@ def anum(self):
return self._mapdl.get_array("AREA", item1="ALIST").astype(np.int32)

@property
def vnum(self):
def vnum(self) -> NDArray[np.integer]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Array of volume numbers.

Examples
Expand All @@ -450,7 +475,7 @@ def vnum(self):
return self._mapdl.get_array("VOLU", item1="VLIST").astype(np.int32)

@supress_logging
def _load_lines(self):
def _load_lines(self) -> pv.MultiBlock:
"""Load lines from MAPDL using IGES"""
# ignore volumes
self._mapdl.cm("__tmp_volu__", "VOLU", mute=True)
Expand Down Expand Up @@ -488,19 +513,20 @@ def _load_lines(self):
entity_num = int(line.d["entity_subs_num"])
if entity_num not in entity_nums and entity_num in selected_lnum:
entity_nums.append(entity_num)
line = line.to_vtk(resolution=100)
line = line.to_vtk(resolution=1)
line.cell_data["entity_num"] = entity_num
lines.append(line)

if lines:
lines = merge_polydata(lines)
lines["entity_num"] = lines["entity_num"].astype(np.int32)
lines_ = pv.MultiBlock()
for line in lines:
lines_.append(line)
else:
lines = pv.PolyData()
lines_ = pv.MultiBlock()

return lines
return lines_

def _load_keypoints(self):
def _load_keypoints(self) -> pv.PolyData:
"""Load keypoints from MAPDL using IGES"""
germa89 marked this conversation as resolved.
Show resolved Hide resolved
# write only keypoints
self._mapdl.cm("__tmp_volu__", "VOLU", mute=True)
Expand All @@ -527,7 +553,7 @@ def _load_keypoints(self):
keypoints_pd["entity_num"] = kp_num
return keypoints_pd

def __str__(self):
def __str__(self) -> str:
"""Current geometry info"""
info = "MAPDL Selected Geometry\n"
info += "Keypoints: %d\n" % self.n_keypoint
Expand All @@ -536,7 +562,9 @@ def __str__(self):
info += "Volumes: %d\n" % self.n_volu
return info

def keypoint_select(self, items, sel_type="S", return_selected=False):
def keypoint_select(
self, items, sel_type="S", return_selected=False
) -> Optional[int]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Select keypoints using a sequence of items.

Parameters
Expand Down Expand Up @@ -613,7 +641,12 @@ def keypoint_select(self, items, sel_type="S", return_selected=False):
if return_selected:
return self.knum

def line_select(self, items, sel_type="S", return_selected=False):
def line_select(
self,
items: Optional[Sequence],
germa89 marked this conversation as resolved.
Show resolved Hide resolved
germa89 marked this conversation as resolved.
Show resolved Hide resolved
sel_type: Optional[str] = "S",
Gryfenfer97 marked this conversation as resolved.
Show resolved Hide resolved
return_selected: Optional[bool] = False,
germa89 marked this conversation as resolved.
Show resolved Hide resolved
germa89 marked this conversation as resolved.
Show resolved Hide resolved
) -> Optional[int]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Select lines using a sequence of items.

Parameters
Expand Down Expand Up @@ -690,7 +723,12 @@ def line_select(self, items, sel_type="S", return_selected=False):
if return_selected:
return self.lnum

def area_select(self, items, sel_type="S", return_selected=False):
def area_select(
self,
items: Optional[Sequence],
sel_type: Optional[str] = "S",
return_selected: Optional[bool] = False,
germa89 marked this conversation as resolved.
Show resolved Hide resolved
) -> Optional[int]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Select areas using a sequence of items.

Parameters
Expand Down Expand Up @@ -767,7 +805,35 @@ def area_select(self, items, sel_type="S", return_selected=False):
if return_selected:
return self.anum

def volume_select(self, items, sel_type="S", return_selected=False):
@property
def volumes(self) -> pv.MultiBlock:
"""Get volumes from MAPDL represented as ``pyvista.MultiBlock``"""
return self.get_volumes(return_as_list=False)

def get_volumes(self, return_as_list: bool = True) -> Union[list, pv.MultiBlock]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""List of volumes from MAPDL represented as ``pyvista.UnstructuredGrid``"""

# Cache current selection
self._mapdl.cm("__temp_volu__", "volu")

if return_as_list:
volumes_ = []
else:
volumes_ = pv.MultiBlock()

for each_volu in self.vnum:
self._mapdl.vsel("S", vmin=each_volu)
volumes_.append(self._mapdl.mesh.grid.copy())

self._mapdl.cmsel("S", "__temp_volu__")
return volumes_

def volume_select(
self,
items: Optional[Sequence],
sel_type: Optional[str] = "S",
return_selected: Optional[bool] = False,
) -> Optional[int]:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Select volumes using a sequence of items.

Parameters
Expand Down Expand Up @@ -844,7 +910,7 @@ def volume_select(self, items, sel_type="S", return_selected=False):
if return_selected:
return self.vnum

def _select_items(self, items, item_type, sel_type):
def _select_items(self, items: Sequence, item_type: str, sel_type: str) -> None:
germa89 marked this conversation as resolved.
Show resolved Hide resolved
"""Select items using FLST
germa89 marked this conversation as resolved.
Show resolved Hide resolved

Parameters
Expand Down
26 changes: 22 additions & 4 deletions tests/test_geometry.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Test geometry commands"""
import math

import numpy as np
import pytest
import pyvista as pv


def test_keypoint_selection(mapdl, cleared):
Expand Down Expand Up @@ -170,7 +170,7 @@ def test_kdist(cleared, mapdl):
knum1 = mapdl.k("", *kp1)
kpdist, xdist, ydist, zdist = mapdl.kdist(knum0, knum1)
assert kpdist == round(
math.sqrt(
np.sqrt(
(kp1[0] - kp0[0]) ** 2 + (kp1[1] - kp0[1]) ** 2 + (kp1[2] - kp0[2]) ** 2
),
7,
Expand Down Expand Up @@ -474,7 +474,7 @@ def test_ndist(cleared, mapdl):
node_num2 = mapdl.n("", *node2)
node_dist, node_xdist, node_ydist, node_zdist = mapdl.ndist(node_num1, node_num2)
assert node_dist == round(
math.sqrt(
np.sqrt(
(node2[0] - node1[0]) ** 2
+ (node2[1] - node1[1]) ** 2
+ (node2[2] - node1[2]) ** 2
Expand All @@ -493,3 +493,21 @@ def test_empty_model(mapdl):
assert mapdl.geometry.lnum.size == 0
assert mapdl.geometry.anum.size == 0
assert mapdl.geometry.vnum.size == 0


@pytest.mark.parametrize(
"entity,number", (["keypoints", 8], ["lines", 12], ["areas", 6], ["volumes", 1])
)
def test_entities_simple_cube(mapdl, cube_solve, entity, number):
entity = getattr(mapdl.geometry, entity)
assert len(entity) == number
assert isinstance(entity, pv.MultiBlock)


@pytest.mark.parametrize(
"entity,number", (["keypoints", 26], ["lines", 45], ["areas", 28], ["volumes", 6])
)
def test_entities_multiple_bodies(mapdl, contact_solve, entity, number):
entity = getattr(mapdl.geometry, entity)
assert len(entity) == number
assert isinstance(entity, pv.MultiBlock)