diff --git a/doc/source/user_guide/components.rst b/doc/source/user_guide/components.rst new file mode 100644 index 0000000000..4b0e8603d9 --- /dev/null +++ b/doc/source/user_guide/components.rst @@ -0,0 +1,97 @@ + +.. _ref_components: + +******************* +Managing components +******************* + +MAPDL components can be retrieved and set using +:attr:`Mapdl.components `. + + +There are several ways to create a component in MAPDL. + +You can use the :meth:`Mapdl.cm ` method: + +.. code:: pycon + + >>> from ansys.mapdl.core import launch_mapdl + >>> mapdl = launch_mapdl() + >>> mapdl.prep7() + >>> mapdl.block(0, 1, 0, 1, 0, 1) + >>> mapdl.vsel("s", "", "", 1) + >>> mapdl.cm("my_comp", "volu") + +Or use higher level syntax. For instance, to set a component +specifying the type and items: + +.. code:: pycon + + >>> mapdl.components["mycomp3"] = "KP", [1, 2, 3] + +Set a component without specifying the type, by default it is ``NODE``: + +.. code:: pycon + + >>> mapdl.components["mycomp4"] = (1, 2, 3) + /Users/german.ayuso/pymapdl/src/ansys/mapdl/core/component.py:347: UserWarning: Assuming a KP selection. + It is recommended you use the following notation to avoid this warning: + > mapdl.components['mycomp4'] = 'KP' (1, 2, 3) + Alternatively, you disable this warning using: + > mapdl.components.default_entity_warning=False + warnings.warn( + +You can change the default type by changing +:attr:`Mapdl.components.default_entity ` + +.. code:: pycon + + >>> mapdl.components.default_entity = "KP" + >>> mapdl.components["mycomp"] = [1, 2, 3] + >>> mapdl.components["mycomp"].type + 'KP' + +You can also create a component from already selected entities: + +.. code:: pycon + + >>> mapdl.lsel("S", 1, 2) + >>> mapdl.components["mylinecomp"] = "LINE" + >>> mapdl.components["mylinecomp"] + (1, 2) + + +Selecting a component and retrieving it: + +.. code:: pycon + + >>> mapdl.cmsel("s", "mycomp3") + >>> mapdl.components["mycomp3"] + Component(type='KP', items=(1, 2, 3)) + + +.. note:: Component selection + To being able to access a component through :attr:`Mapdl.components `, + the component needs to be selected using :meth:`Mapdl.cmsel() `. + + +Component object +================ + +The `Component object ` is the object returned by +:attr:`Mapdl.components ` when you query it with a component name. +This object has two main attributes: `type ` and `items `. +The former returns the component type (`"ELEM"`, `"NODE"`, `"KP"`, etc) and the later returns +a tuple with the index of the entities which belong to that component. + +.. code:: pycon + + >>> comp = mapdl.components["mycomp3"] + >>> comp.type + 'KP' + >>> comp.items + (1, 2, 3) + +It should be noticed that the component object is not linked to the MAPDL component, so any change on it +is not reflected in the MAPDL counterpart. + diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index e94fbbc485..ca2df903cb 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -21,6 +21,7 @@ This section provides a general overview of PyMAPDL and how you use it. mesh_geometry post parameters + components database convert math diff --git a/doc/source/user_guide/parameters.rst b/doc/source/user_guide/parameters.rst index f8cf0a8739..51a3453b44 100644 --- a/doc/source/user_guide/parameters.rst +++ b/doc/source/user_guide/parameters.rst @@ -4,7 +4,8 @@ ********************************* Setting and retrieving parameters ********************************* -APDL parameters can be retrieved from and instance of + +MAPDL parameters can be retrieved from an instance of :class:`Mapdl ` using the :attr:`Mapdl.parameters `. For example, if you want to use MAPDL's diff --git a/examples/00-mapdl-examples/2d_pressure_vessel.py b/examples/00-mapdl-examples/2d_pressure_vessel.py index f9eaa5c64a..ea0ac50c36 100644 --- a/examples/00-mapdl-examples/2d_pressure_vessel.py +++ b/examples/00-mapdl-examples/2d_pressure_vessel.py @@ -73,7 +73,7 @@ def pipe_plane_strain(e, nu, inn_radius, out_radius, press, aesize): # We perform plane strain analysis on one quadrant (0deg - 90deg) of the # pressure vessel mapdl.pcirc(inn_radius, out_radius, theta1=0, theta2=90) - mapdl.cm("PIPE_PROFILE", "AREA") + mapdl.components["PIPE_PROFILE"] = "AREA" # Define material properties mapdl.mp("EX", 1, e) # Youngs modulus @@ -88,14 +88,14 @@ def pipe_plane_strain(e, nu, inn_radius, out_radius, press, aesize): # Create components for defining loads and constraints mapdl.nsel("S", "LOC", "X", 0) # Select nodes on top left edge - mapdl.cm("X_FIXED", "NODES") # Create nodal component + mapdl.components["X_FIXED"] = "NODES" # Create nodal component mapdl.nsel("S", "LOC", "Y", 0) # Select nodes on bottom right edge - mapdl.cm("Y_FIXED", "NODES") # Create nodal component + mapdl.components["Y_FIXED"] = "NODES" # Create nodal component mapdl.allsel() mapdl.lsel("S", "RADIUS", vmin=rad1) # Select the line along inner radius - mapdl.cm("PRESSURE_EDGE", "LINE") # Create a line component + mapdl.components["PRESSURE_EDGE"] = "LINE" # Create a line component mapdl.allsel() # Define solution controls diff --git a/examples/00-mapdl-examples/composite_dcb.py b/examples/00-mapdl-examples/composite_dcb.py index 5879160dee..a8b2093d09 100644 --- a/examples/00-mapdl-examples/composite_dcb.py +++ b/examples/00-mapdl-examples/composite_dcb.py @@ -145,7 +145,7 @@ mapdl.geometry.area_select(areas[0], "r") mapdl.nsla("r", 1) mapdl.nsel("r", "loc", "x", pre_crack, length + pre_crack + eps) -mapdl.cm("cm_1", "node") +mapdl.components["cm_1"] = "node" mapdl.allsel() mapdl.asel("s", "loc", "z", 1.7) @@ -153,11 +153,11 @@ mapdl.geometry.area_select(areas[1], "r") mapdl.nsla("r", 1) mapdl.nsel("r", "loc", "x", pre_crack, length + pre_crack + eps) -mapdl.cm("cm_2", "node") +mapdl.components["cm_2"] = "node" # Identify all the elements before generation of TARGE170 elements mapdl.allsel() -mapdl.cm("_elemcm", "elem") +mapdl.components["_elemcm"] = "elem" mapdl.mat(2) # Assign real constants and key options @@ -181,7 +181,7 @@ # Generate TARGE170 elements on top of cm_1 mapdl.nsel("s", "", "", "cm_1") -mapdl.cm("_target", "node") +mapdl.components["_target"] = "node" mapdl.type(2) mapdl.esln("s", 0) mapdl.esurf() @@ -189,7 +189,7 @@ # Generate CONTA174 elements on top of cm_2 mapdl.cmsel("s", "_elemcm") mapdl.nsel("s", "", "", "cm_2") -mapdl.cm("_contact", "node") +mapdl.components["_contact"] = "node" mapdl.type(3) mapdl.esln("s", 0) mapdl.esurf() @@ -211,13 +211,13 @@ mapdl.nsel(type_="s", item="loc", comp="x", vmin=0.0, vmax=0.0) mapdl.nsel(type_="r", item="loc", comp="z", vmin=2 * height, vmax=2 * height) mapdl.d(node="all", lab="uz", value=d) -mapdl.cm("top_nod", "node") +mapdl.components["top_nod"] = "node" mapdl.allsel() mapdl.nsel(type_="s", item="loc", comp="x", vmin=0.0, vmax=0.0) mapdl.nsel(type_="r", item="loc", comp="z", vmin=0.0, vmax=0.0) mapdl.d(node="all", lab="uz", value=-10) -mapdl.cm("bot_nod", "node") +mapdl.components["bot_nod"] = "node" # Apply the fix condition mapdl.allsel() diff --git a/src/ansys/mapdl/core/component.py b/src/ansys/mapdl/core/component.py index ef67f93458..c32e0f9530 100644 --- a/src/ansys/mapdl/core/component.py +++ b/src/ansys/mapdl/core/component.py @@ -142,6 +142,11 @@ def type(self) -> ENTITIES_TYP: """Return the type of the component. For instance "NODES", "KP", etc.""" return self._type + @property + def items(self) -> tuple: + """Return the ids of the entities in the component.""" + return tuple(self) + class ComponentManager: """Collection of MAPDL components. @@ -175,15 +180,17 @@ class ComponentManager: Set a component without specifying the type, by default it is ``NODE``: >>> mapdl.components["mycomp4"] = (1, 2, 3) + /Users/german.ayuso/pymapdl/src/ansys/mapdl/core/component.py:282: UserWarning: Assuming a NODES selection. + It is recommended you use the following notation to avoid this warning: + \>\>\> mapdl.components['mycomp3'] = 'NODES' (1, 2, 3) + Alternatively, you disable this warning using: + > mapdl.components.default_entity_warning=False + warnings.warn( You can change the default type by changing :attr:`Mapdl.components.default_entity ` >>> mapdl.component.default_entity = "KP" - /Users/german.ayuso/pymapdl/src/ansys/mapdl/core/component.py:282: UserWarning: Assuming a NODES selection. - It is recommended you use the following notation to avoid this warning: - \>\>\> mapdl.components['mycomp3'] = 'NODES' (1, 2, 3) - Alternatively, you disable this warning using >>> mapdl.component["mycomp] = [1, 2, 3] >>> mapdl.component["mycomp"].type 'KP' @@ -274,7 +281,6 @@ def _comp(self, value): self.__comp = value def __getitem__(self, key: str) -> ITEMS_VALUES: - self._comp = self._mapdl._parse_cmlist() forced_to_select = False if key.upper() not in self._comp and self._autoselect_components: @@ -388,19 +394,17 @@ def __setitem__(self, key: str, value: ITEMS_VALUES) -> None: _check_valid_pyobj_to_entities(cmitems) - # Save current selection - self._mapdl.cm("__temp__", cmtype) - - # Select the cmitems entities - func = getattr(self._mapdl, ENTITIES_MAPPING[cmtype].lower()) - func(type_="S", vmin=cmitems) + # Using context manager to proper save the selections (including CM) + with self._mapdl.save_selection: + # Select the cmitems entities + func = getattr(self._mapdl, ENTITIES_MAPPING[cmtype].lower()) + func(type_="S", vmin=cmitems) - # create component - self._mapdl.cm(cmname, cmtype) + # create component + self._mapdl.cm(cmname, cmtype) - # reselecting previous selection - self._mapdl.cmsel("S", "__temp__") - self._mapdl.cmdele("__temp__") + # adding newly created selection + self._mapdl.cmsel("A", cmname) def __repr__(self) -> str: """Return the current components in a pretty format""" @@ -499,3 +503,14 @@ def select(self, names: Union[str, list[str], tuple[str]], mute=False) -> None: self._mapdl.cmsel("S", each_name, mute=mute) else: self._mapdl.cmsel("A", each_name, mute=mute) + + def _get_all_components_type(self, type_: ENTITIES_TYP): + """Returns a dict with all the components which type matches the entity type""" + dict_ = {} + for each_comp in self._comp: + item = self.__getitem__(each_comp) + i_type_ = item.type + i_items = item.items + if i_type_ == type_: + dict_[each_comp] = i_items + return dict_ diff --git a/src/ansys/mapdl/core/mapdl.py b/src/ansys/mapdl/core/mapdl.py index 1a526b2aaa..16144d18e9 100644 --- a/src/ansys/mapdl/core/mapdl.py +++ b/src/ansys/mapdl/core/mapdl.py @@ -4911,12 +4911,19 @@ def _parse_cmlist(self, cmlist: Optional[str] = None) -> Dict[str, Any]: if not cmlist: cmlist = self.cmlist() - header = "NAME TYPE SUBCOMPONENTS" - blocks = re.findall( - r"(?s)NAME\s+TYPE\s+SUBCOMPONENTS\s+(.*?)\s*(?=\n\s*\n|\Z)", - cmlist, - flags=re.DOTALL, - ) + if "NAME" in cmlist and "SUBCOMPONENTS" in cmlist: + # header + # "NAME TYPE SUBCOMPONENTS" + blocks = re.findall( + r"(?s)NAME\s+TYPE\s+SUBCOMPONENTS\s+(.*?)\s*(?=\n\s*\n|\Z)", + cmlist, + flags=re.DOTALL, + ) + elif "LIST ALL SELECTED COMPONENTS": + blocks = cmlist.splitlines()[1:] + else: + raise ValueError("The format of the CMLIST output is not recognaised.") + cmlist = "\n".join(blocks) def extract(each_line, ind): @@ -4934,9 +4941,15 @@ def _parse_cmlist_indiv( if not cmlist: cmlist = self.cmlist(cmname, 1) # Capturing blocks + if "NAME" in cmlist and "SUBCOMPONENTS" in cmlist: + header = r"NAME\s+TYPE\s+SUBCOMPONENTS" + + elif "LIST COMPONENT" in cmlist: + header = "" + cmlist = "\n\n".join( re.findall( - r"(?s)NAME\s+TYPE\s+SUBCOMPONENTS\s+(.*?)\s*(?=\n\s*\n|\Z)", + r"(?s)" + header + r"\s+(.*?)\s*(?=\n\s*\n|\Z)", cmlist, flags=re.DOTALL, ) diff --git a/src/ansys/mapdl/core/mesh_grpc.py b/src/ansys/mapdl/core/mesh_grpc.py index 95dc61565c..d9e5c69f30 100644 --- a/src/ansys/mapdl/core/mesh_grpc.py +++ b/src/ansys/mapdl/core/mesh_grpc.py @@ -104,7 +104,6 @@ def _reset_cache(self): self._cached_elements = None # cached list of elements self._chunk_size = None self._elem = None - self._elem_comps = {} self._elem_off = None self._enum = None # cached element numbering self._esys = None # cached element coordinate system @@ -117,7 +116,6 @@ def _reset_cache(self): self._mtype = None # cached ansys material type self._nnum = None self._node_angles = None # cached node angles - self._node_comps = {} self._node_coord = None # cached node coordinates self._rcon = None # cached ansys element real constant self._rdat = None @@ -325,7 +323,7 @@ def element_components(self): array of MAPDL element numbers corresponding to the element component. The keys are element component names. """ - return self._elem_comps + return self._mapdl.components._get_all_components_type("ELEM") @property def node_components(self): @@ -335,7 +333,7 @@ def node_components(self): array of MAPDL node numbers corresponding to the node component. The keys are node component names. """ - return self._node_comps + return self._mapdl.components._get_all_components_type("NODE") @property @requires_model() diff --git a/tests/test_component.py b/tests/test_component.py index b76f3d7140..c4807be74a 100644 --- a/tests/test_component.py +++ b/tests/test_component.py @@ -43,7 +43,7 @@ def test_set_item(mapdl, cube_geom_and_mesh, type_): mapdl.components[comp_name] = type_, [1, 2, 3] cm_ = mapdl.run("cmlist").upper() - assert comp_name not in cm_ + assert comp_name in cm_ # the component should be selected already after creation mapdl.cmsel("S", comp_name) cm_ = mapdl.run("cmlist").upper() @@ -68,6 +68,7 @@ def test_set_item_no_type(mapdl, cube_geom_and_mesh): def test_get_item(mapdl, cube_geom_and_mesh): mapdl.components["mycomp"] = "node", [1, 2, 3] + mapdl.cmsel("NONE") with pytest.raises(ComponentIsNotSelected): mapdl.components["mycomp"] @@ -83,6 +84,7 @@ def test_get_item(mapdl, cube_geom_and_mesh): def test_get_item_autoselect_components(mapdl, cube_geom_and_mesh): mapdl.components["mycomp"] = "node", [1, 2, 3] + mapdl.cmsel("NONE") mapdl.components._autoselect_components = True cm_ = mapdl.run("cmlist").upper() @@ -268,3 +270,33 @@ def test_dunder_methods_types(mapdl, basic_components): def test_dunder_methods_items(mapdl, basic_components): assert [("MYCOMP1", "NODE"), ("MYCOMP2", "KP")] == list(mapdl.components.items()) + + +def test__get_all_components_type(mapdl, cube_geom_and_mesh): + mapdl.allsel() + mapdl.esel("s", "", "", 1) + mapdl.nsel("s", "", "", 1) + mapdl.cm("cmelem", "ELEM") + mapdl.cm("cmnodes", "NODE") + + mapdl.nsel("a", "", "", 2) + mapdl.esel("a", "", "", 2) + mapdl.cm("cmnodes2", "NODE") + mapdl.cm("cmelem2", "ELEM") + + comp_elem = mapdl.components._get_all_components_type("ELEM") + + expected_output = {"CMELEM": (1,), "CMELEM2": (1, 2)} + assert comp_elem + assert comp_elem == expected_output + assert "CMNODES" not in comp_elem + assert "CMNODES2" not in comp_elem + + # Nodes + comp_nodes = mapdl.components._get_all_components_type("NODE") + + expected_output = {"CMNODES": (1,), "CMNODES2": (1, 2)} + assert comp_nodes + assert comp_nodes == expected_output + assert "CMELEM" not in comp_nodes + assert "CMELEM2" not in comp_nodes diff --git a/tests/test_mesh_grpc.py b/tests/test_mesh_grpc.py index 40de300285..ed44252e33 100644 --- a/tests/test_mesh_grpc.py +++ b/tests/test_mesh_grpc.py @@ -104,6 +104,53 @@ def test_empty_mesh(mapdl, cleared): mapdl.mesh.save("file.vtk") +def test_element_node_components(mapdl, contact_geom_and_mesh): + mapdl.allsel() + assert not mapdl.mesh.element_components + assert "MYELEMCOMP" not in mapdl.mesh.element_components + + assert mapdl.mesh.node_components + assert "TN.TGT" in mapdl.mesh.node_components + assert "CMNODE" not in mapdl.mesh.node_components + + mapdl.cmsel("NONE") + mapdl.cm("CMNODE", "NODE") + mapdl.components["MYELEMCOMP"] = "ELEM", (1, 2, 3) + + assert mapdl.mesh.element_components + assert "MYELEMCOMP" in mapdl.mesh.element_components + + assert mapdl.mesh.node_components + assert "TN.TGT" not in mapdl.mesh.node_components + assert "CMNODE" in mapdl.mesh.node_components + + mapdl.cmsel("NONE") + assert not mapdl.mesh.element_components + assert "MYELEMCOMP" not in mapdl.mesh.element_components + + assert not mapdl.mesh.node_components + assert "TN.TGT" not in mapdl.mesh.node_components + assert "CMNODE" not in mapdl.mesh.node_components + + mapdl.cmsel("S", "MYELEMCOMP") + assert mapdl.mesh.element_components + assert "MYELEMCOMP" in mapdl.mesh.element_components + + assert not mapdl.mesh.node_components + assert "TN.TGT" not in mapdl.mesh.node_components + assert "CMNODE" not in mapdl.mesh.node_components + + mapdl.cmsel("S", "CMNODE") + assert mapdl.mesh.node_components + assert "CMNODE" in mapdl.mesh.node_components + assert "TN.TGT" not in mapdl.mesh.node_components + + mapdl.cmsel("all") + assert mapdl.mesh.node_components + assert "TN.TGT" in mapdl.mesh.node_components + assert "CMNODE" in mapdl.mesh.node_components + + def test_non_empty_mesh(mapdl, contact_geom_and_mesh): assert mapdl.mesh.n_node > 0 assert mapdl.mesh.n_elem > 0 @@ -134,8 +181,15 @@ def test_non_empty_mesh(mapdl, contact_geom_and_mesh): assert mapdl.mesh.tshape_key assert len(mapdl.mesh.tshape_key.keys()) > 0 - # assert mapdl.mesh.element_components #Not implemented - # assert mapdl.mesh.node_components # Not implemented + mapdl.allsel() + mapdl.cmsel("all") + mapdl.cm("CMNODE", "NODE") + mapdl.components["MYELEMCOMP"] = "ELEM", (1, 2, 3) + + assert mapdl.mesh.element_components + assert "MYELEMCOMP" in mapdl.mesh.element_components + assert mapdl.mesh.node_components + assert "CMNODE" in mapdl.mesh.node_components # bools assert mapdl.mesh._has_elements