diff --git a/doc/changelog.d/2180.added.md b/doc/changelog.d/2180.added.md new file mode 100644 index 0000000000..5dc61f7eba --- /dev/null +++ b/doc/changelog.d/2180.added.md @@ -0,0 +1 @@ +Remove materials diff --git a/src/ansys/geometry/core/_grpc/_services/base/bodies.py b/src/ansys/geometry/core/_grpc/_services/base/bodies.py index 91cb28f8ec..957e17f260 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/bodies.py +++ b/src/ansys/geometry/core/_grpc/_services/base/bodies.py @@ -149,6 +149,11 @@ def get_assigned_material(self, **kwargs) -> dict: """Get the assigned material of a body.""" pass + @abstractmethod + def remove_assigned_material(self, **kwargs) -> dict: + """Remove the assigned material of a body.""" + pass + @abstractmethod def set_name(self, **kwargs) -> dict: """Set the name of a body.""" diff --git a/src/ansys/geometry/core/_grpc/_services/base/materials.py b/src/ansys/geometry/core/_grpc/_services/base/materials.py index bceb66e8fc..c504bf63ce 100644 --- a/src/ansys/geometry/core/_grpc/_services/base/materials.py +++ b/src/ansys/geometry/core/_grpc/_services/base/materials.py @@ -43,3 +43,8 @@ def __init__(self, channel: grpc.Channel): def add_material(self, **kwargs) -> dict: """Add material to the service design.""" pass + + @abstractmethod + def remove_material(self, **kwargs) -> dict: + """Remove material from the service design.""" + pass diff --git a/src/ansys/geometry/core/_grpc/_services/v0/bodies.py b/src/ansys/geometry/core/_grpc/_services/v0/bodies.py index fdc862eaf7..a55186fbf5 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/bodies.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/bodies.py @@ -495,6 +495,19 @@ def get_assigned_material(self, **kwargs) -> dict: # noqa: D102 # Return the response - formatted as a dictionary return {"material": from_grpc_material_to_material(resp)} + @protect_grpc + def remove_assigned_material(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.bodies_pb2 import RemoveAssignedMaterialRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = RemoveAssignedMaterialRequest(ids=[build_grpc_id(id) for id in kwargs["ids"]]) + + # Call the gRPC service + resp = self.stub.RemoveAssignedMaterial(request=request) + + # Return the response - formatted as a dictionary + return {"successfully_removed": [id for id in resp.successfully_removed]} + @protect_grpc def set_name(self, **kwargs) -> dict: # noqa: D102 from ansys.api.geometry.v0.bodies_pb2 import SetNameRequest diff --git a/src/ansys/geometry/core/_grpc/_services/v0/materials.py b/src/ansys/geometry/core/_grpc/_services/v0/materials.py index df9da9b717..e71b4b1b02 100644 --- a/src/ansys/geometry/core/_grpc/_services/v0/materials.py +++ b/src/ansys/geometry/core/_grpc/_services/v0/materials.py @@ -62,3 +62,18 @@ def add_material(self, **kwargs) -> dict: # noqa: D102 # Convert the response to a dictionary return {} + + @protect_grpc + def remove_material(self, **kwargs) -> dict: # noqa: D102 + from ansys.api.geometry.v0.materials_pb2 import RemoveFromDocumentRequest + + # Create the request - assumes all inputs are valid and of the proper type + request = RemoveFromDocumentRequest( + request_data=[from_material_to_grpc_material(mat) for mat in kwargs["materials"]] + ) + + # Call the gRPC service + _ = self.stub.RemoveFromDocument(request=request) + + # Convert the response to a dictionary + return {} diff --git a/src/ansys/geometry/core/_grpc/_services/v1/bodies.py b/src/ansys/geometry/core/_grpc/_services/v1/bodies.py index 29f350bdcc..40f6b6d50c 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/bodies.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/bodies.py @@ -136,6 +136,10 @@ def set_assigned_material(self, **kwargs) -> dict: # noqa: D102 def get_assigned_material(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + @protect_grpc + def remove_assigned_material(self, **kwargs): # noqa: D102 + raise NotImplementedError + @protect_grpc def set_name(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError diff --git a/src/ansys/geometry/core/_grpc/_services/v1/materials.py b/src/ansys/geometry/core/_grpc/_services/v1/materials.py index 4b54d626b6..acf0f39b2c 100644 --- a/src/ansys/geometry/core/_grpc/_services/v1/materials.py +++ b/src/ansys/geometry/core/_grpc/_services/v1/materials.py @@ -50,3 +50,7 @@ def __init__(self, channel: grpc.Channel): # noqa: D102 @protect_grpc def add_material(self, **kwargs) -> dict: # noqa: D102 raise NotImplementedError + + @protect_grpc + def remove_material(self, **kwargs) -> dict: # noqa: D102 + raise NotImplementedError diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index c40589f5fd..6d6c6422b9 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -338,6 +338,11 @@ def get_assigned_material(self) -> Material: """ return + @abstractmethod + def remove_assigned_material(self) -> None: + """Remove the material assigned to the body.""" + return + @abstractmethod def add_midsurface_thickness(self, thickness: Quantity) -> None: """Add a mid-surface thickness to a surface body. @@ -1061,6 +1066,11 @@ def get_assigned_material(self) -> Material: # noqa: D102 response = self._grpc_client.services.bodies.get_assigned_material(id=self.id) return response.get("material") + @min_backend_version(26, 1, 0) + def remove_assigned_material(self) -> None: # noqa: D102 + self._grpc_client.log.debug(f"Removing assigned material for body {self.id}.") + self._grpc_client.services.bodies.remove_assigned_material(ids=[self.id]) + @protect_grpc @check_input_types def add_midsurface_thickness(self, thickness: Quantity) -> None: # noqa: D102 @@ -1593,6 +1603,10 @@ def assign_material(self, material: Material) -> None: # noqa: D102 def get_assigned_material(self) -> Material: # noqa: D102 return self._template.get_assigned_material() + @ensure_design_is_active + def remove_assigned_material(self): # noqa: D102 + self._template.remove_assigned_material() + @ensure_design_is_active def add_midsurface_thickness(self, thickness: Quantity) -> None: # noqa: D102 self._template.add_midsurface_thickness(thickness) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 670c1e73e7..172f3d21af 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -227,6 +227,24 @@ def add_material(self, material: Material) -> None: self._materials.append(material) self._grpc_client.log.debug(f"Material {material.name} is successfully added to design.") + @min_backend_version(26, 1, 0) + @check_input_types + @ensure_design_is_active + def remove_material(self, material: Material | list[Material]) -> None: + """Remove a material from the design. + + Parameters + ---------- + material : Material | list[Material] + Material or list of materials to remove. + """ + material = material if isinstance(material, list) else [material] + + self._grpc_client.services.materials.remove_material(materials=material) + for mat in material: + self._materials.remove(mat) + self._grpc_client.log.debug(f"Material {mat.name} is successfully removed from design.") + @check_input_types @ensure_design_is_active def save(self, file_location: Path | str, write_body_facets: bool = False) -> None: diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index ec2bcf14fe..d5236b93a1 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -322,6 +322,38 @@ def test_get_empty_material(modeler: Modeler): assert len(mat_service.properties) == 1 +def test_remove_material_from_body(modeler: Modeler): + """Test removing a material from a body.""" + # Create a design and a sketch + design = modeler.create_design("RemoveMaterialTest") + sketch = Sketch() + sketch.circle(Point2D([0, 0], UNITS.mm), Quantity(10, UNITS.mm)) + + # Extrude the sketch to create a body + body = design.extrude_sketch("CircleBody", sketch, Quantity(10, UNITS.mm)) + + # Create and assign a material + density = Quantity(7850, UNITS.kg / (UNITS.m**3)) + material = Material( + "Steel", + density, + [MaterialProperty(MaterialPropertyType.POISSON_RATIO, "Poisson", Quantity(0.3))], + ) + design.add_material(material) + body.assign_material(material) + assert body.material.name == "Steel" + + # Remove the material from the body + body.remove_assigned_material() + + # Check that the body no longer has a material assigned + assert body.material.name == "" + assert len(body.material.properties) == 1 + assert body.material.properties[MaterialPropertyType.DENSITY].quantity == Quantity( + 0, UNITS.kg / (UNITS.m**3) + ) + + def test_face_to_body_creation(modeler: Modeler): """Test in charge of validating the extrusion of an existing face.""" # Create a Sketch and draw a circle (all client side) diff --git a/tests/integration/test_material.py b/tests/integration/test_material.py index 2329a38664..c363f7aa68 100644 --- a/tests/integration/test_material.py +++ b/tests/integration/test_material.py @@ -84,3 +84,41 @@ def test_material_creation(modeler: Modeler): design.materials[0].properties[MaterialPropertyType.POISSON_RATIO].quantity == mat_prop_quantity ) + + +def test_material_removal(modeler: Modeler): + """Test the removal of a material from a design.""" + design = modeler.create_design("my_design") + + mat_name = "mat_1" + density = 1000 * UNITS.kg / (UNITS.m * UNITS.m * UNITS.m) + material = Material(mat_name, density) + design.add_material(material) + + assert design.materials[0].name == mat_name + + design.remove_material(material) + + assert len(design.materials) == 0 + + +def test_remove_multiple_materials(modeler: Modeler): + """Test the removal of multiple materials from a design.""" + design = modeler.create_design("my_design") + + mat_name_1 = "mat_1" + density_1 = 1000 * UNITS.kg / (UNITS.m * UNITS.m * UNITS.m) + material_1 = Material(mat_name_1, density_1) + design.add_material(material_1) + + mat_name_2 = "mat_2" + density_2 = 2000 * UNITS.kg / (UNITS.m * UNITS.m * UNITS.m) + material_2 = Material(mat_name_2, density_2) + design.add_material(material_2) + + assert design.materials[0].name == mat_name_1 + assert design.materials[1].name == mat_name_2 + + design.remove_material([material_1, material_2]) + + assert len(design.materials) == 0