From 89170314d519f3a8b0a11f91149782ecb06cde46 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:50:50 +0200 Subject: [PATCH 1/5] feat: caching bodies to avoid unnecessary object creation --- src/ansys/geometry/core/designer/body.py | 1 + src/ansys/geometry/core/designer/component.py | 17 +++++++- tests/integration/test_design.py | 42 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index cb2d031e91..23056ec313 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -1097,6 +1097,7 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 response.master_id, copy_name, self._grpc_client, is_surface=self.is_surface ) parent._master_component.part.bodies.append(tb) + parent.__clear_cached_bodies() body_id = f"{parent.id}/{tb.id}" if parent.parent_component else tb.id return Body(body_id, response.name, parent, tb) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index fa20639deb..5ed84e7314 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -22,6 +22,7 @@ """Provides for managing components.""" from enum import Enum, unique +from functools import cached_property from typing import TYPE_CHECKING, Any, Optional, Union import uuid @@ -239,6 +240,11 @@ def __init__( self._master_component.occurrences.append(self) + def __clear_cached_bodies(self) -> None: + """Clear the cached bodies.""" + if 'bodies' in self.__dict__: + del self.__dict__['bodies'] + @property def id(self) -> str: """ID of the component.""" @@ -259,7 +265,7 @@ def components(self) -> list["Component"]: """List of ``Component`` objects inside of the component.""" return self._components - @property + @cached_property def bodies(self) -> list[Body]: """List of ``Body`` objects inside of the component.""" bodies = [] @@ -509,6 +515,7 @@ def extrude_sketch( response = self._bodies_stub.CreateExtrudedBody(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -558,6 +565,7 @@ def sweep_sketch( response = self._bodies_stub.CreateSweepingProfile(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -605,6 +613,7 @@ def sweep_chain( response = self._bodies_stub.CreateSweepingChain(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -720,6 +729,7 @@ def extrude_face( tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -753,6 +763,7 @@ def create_sphere(self, name: str, center: Point3D, radius: Distance) -> Body: response = self._bodies_stub.CreateSphereBody(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -819,6 +830,7 @@ def create_body_from_loft_profile( response = self._bodies_stub.CreateExtrudedBodyFromLoftProfiles(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -856,6 +868,7 @@ def create_surface(self, name: str, sketch: Sketch) -> Body: tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -896,6 +909,7 @@ def create_surface_from_face(self, name: str, face: Face) -> Body: tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -936,6 +950,7 @@ def create_body_from_surface(self, name: str, trimmed_surface: TrimmedSurface) - tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=response.is_surface) self._master_component.part.bodies.append(tb) + self.__clear_cached_bodies() return Body(response.id, response.name, self, tb) @check_input_types diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index cca741203f..07595f0851 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -2738,3 +2738,45 @@ def test_surface_body_creation(modeler: Modeler): assert len(design.bodies) == 6 assert not body.is_surface assert body.faces[0].area.m == pytest.approx(39.4784176044 * 2) + +def test_cached_bodies(modeler: Modeler): + """Test verifying that bodies are cached correctly. + + Whenever a new body is created, modified etc. we should make sure that the cache is updated. + """ + design = modeler.create_design("ModelingDemo") + + # Define a sketch + origin = Point3D([0, 0, 10]) + plane = Plane(origin, direction_x=[1, 0, 0], direction_y=[0, 1, 0]) + + # Create a sketch + sketch_box = Sketch(plane) + sketch_box.box(Point2D([20, 20]), 30 * UNITS.m, 30 * UNITS.m) + + sketch_cylinder = Sketch(plane) + sketch_cylinder.circle(Point2D([20, 20]), 5 * UNITS.m) + + design.extrude_sketch( + name="BoxBody", sketch=sketch_box, distance=Distance(30, unit=UNITS.m) + ) + design.extrude_sketch( + name="CylinderBody", sketch=sketch_cylinder, distance=Distance(60, unit=UNITS.m), + ) + + my_bodies = design.bodies + my_bodies_2 = design.bodies + + # We should make sure that the object memeory addresses are the same + for body1, body2 in zip(my_bodies, my_bodies_2): + assert body1 is body2 # We are comparing the memory addresses + assert id(body1) == id(body2) + + design.extrude_sketch( + name="CylinderBody2", sketch=sketch_cylinder, distance=Distance(20, unit=UNITS.m), direction="-" + ) + my_bodies_3 = design.bodies + + for body1, body3 in zip(my_bodies, my_bodies_3): + assert body1 is not body3 + assert id(body1) != id(body3) From cb99f71a4105330296bc837f47c1040202610871 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:51:15 +0200 Subject: [PATCH 2/5] fix: pre-commit --- src/ansys/geometry/core/designer/component.py | 4 +-- tests/integration/test_design.py | 26 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 5ed84e7314..1e9d232f18 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -242,8 +242,8 @@ def __init__( def __clear_cached_bodies(self) -> None: """Clear the cached bodies.""" - if 'bodies' in self.__dict__: - del self.__dict__['bodies'] + if "bodies" in self.__dict__: + del self.__dict__["bodies"] @property def id(self) -> str: diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 07595f0851..e89adcdd13 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -2739,9 +2739,10 @@ def test_surface_body_creation(modeler: Modeler): assert not body.is_surface assert body.faces[0].area.m == pytest.approx(39.4784176044 * 2) + def test_cached_bodies(modeler: Modeler): """Test verifying that bodies are cached correctly. - + Whenever a new body is created, modified etc. we should make sure that the cache is updated. """ design = modeler.create_design("ModelingDemo") @@ -2757,26 +2758,29 @@ def test_cached_bodies(modeler: Modeler): sketch_cylinder = Sketch(plane) sketch_cylinder.circle(Point2D([20, 20]), 5 * UNITS.m) + design.extrude_sketch(name="BoxBody", sketch=sketch_box, distance=Distance(30, unit=UNITS.m)) design.extrude_sketch( - name="BoxBody", sketch=sketch_box, distance=Distance(30, unit=UNITS.m) - ) - design.extrude_sketch( - name="CylinderBody", sketch=sketch_cylinder, distance=Distance(60, unit=UNITS.m), + name="CylinderBody", + sketch=sketch_cylinder, + distance=Distance(60, unit=UNITS.m), ) my_bodies = design.bodies my_bodies_2 = design.bodies - - # We should make sure that the object memeory addresses are the same + + # We should make sure that the object memory addresses are the same for body1, body2 in zip(my_bodies, my_bodies_2): - assert body1 is body2 # We are comparing the memory addresses + assert body1 is body2 # We are comparing the memory addresses assert id(body1) == id(body2) - + design.extrude_sketch( - name="CylinderBody2", sketch=sketch_cylinder, distance=Distance(20, unit=UNITS.m), direction="-" + name="CylinderBody2", + sketch=sketch_cylinder, + distance=Distance(20, unit=UNITS.m), + direction="-", ) my_bodies_3 = design.bodies - + for body1, body3 in zip(my_bodies, my_bodies_3): assert body1 is not body3 assert id(body1) != id(body3) From 4f23050829a972c9ba01acf12d4b63359fc3b266 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 25 Oct 2024 07:53:23 +0000 Subject: [PATCH 3/5] chore: adding changelog file 1513.added.md [dependabot-skip] --- doc/changelog.d/1513.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/1513.added.md diff --git a/doc/changelog.d/1513.added.md b/doc/changelog.d/1513.added.md new file mode 100644 index 0000000000..def58b8113 --- /dev/null +++ b/doc/changelog.d/1513.added.md @@ -0,0 +1 @@ +caching bodies to avoid unnecessary object creation \ No newline at end of file From 2626a394a9588fcf09f707e4c44f18f682f0dc76 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:26:03 +0100 Subject: [PATCH 4/5] fix: replace leading "__" by "_" - faking privacy --- src/ansys/geometry/core/designer/body.py | 2 +- src/ansys/geometry/core/designer/component.py | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index 23056ec313..d1738a3fde 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -1097,7 +1097,7 @@ def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 response.master_id, copy_name, self._grpc_client, is_surface=self.is_surface ) parent._master_component.part.bodies.append(tb) - parent.__clear_cached_bodies() + parent._clear_cached_bodies() body_id = f"{parent.id}/{tb.id}" if parent.parent_component else tb.id return Body(body_id, response.name, parent, tb) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 1e9d232f18..fd8bb27360 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -240,7 +240,7 @@ def __init__( self._master_component.occurrences.append(self) - def __clear_cached_bodies(self) -> None: + def _clear_cached_bodies(self) -> None: """Clear the cached bodies.""" if "bodies" in self.__dict__: del self.__dict__["bodies"] @@ -515,7 +515,7 @@ def extrude_sketch( response = self._bodies_stub.CreateExtrudedBody(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -565,7 +565,7 @@ def sweep_sketch( response = self._bodies_stub.CreateSweepingProfile(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -613,7 +613,7 @@ def sweep_chain( response = self._bodies_stub.CreateSweepingChain(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @min_backend_version(24, 2, 0) @@ -729,7 +729,7 @@ def extrude_face( tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -763,7 +763,7 @@ def create_sphere(self, name: str, center: Point3D, radius: Distance) -> Body: response = self._bodies_stub.CreateSphereBody(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -830,7 +830,7 @@ def create_body_from_loft_profile( response = self._bodies_stub.CreateExtrudedBodyFromLoftProfiles(request) tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=False) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -868,7 +868,7 @@ def create_surface(self, name: str, sketch: Sketch) -> Body: tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -909,7 +909,7 @@ def create_surface_from_face(self, name: str, face: Face) -> Body: tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=True) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @protect_grpc @@ -950,7 +950,7 @@ def create_body_from_surface(self, name: str, trimmed_surface: TrimmedSurface) - tb = MasterBody(response.master_id, name, self._grpc_client, is_surface=response.is_surface) self._master_component.part.bodies.append(tb) - self.__clear_cached_bodies() + self._clear_cached_bodies() return Body(response.id, response.name, self, tb) @check_input_types From 233430e081db86b5240b609cd41e5bb583396e6b Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:21:31 +0100 Subject: [PATCH 5/5] fix: missing clear cached on test --- src/ansys/geometry/core/designer/component.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index fd8bb27360..3fe923476e 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -1151,6 +1151,7 @@ def delete_body(self, body: Body | str) -> None: # on the client side body_requested._is_alive = False self._grpc_client.log.debug(f"Body {body_requested.id} has been deleted.") + self._clear_cached_bodies() else: self._grpc_client.log.warning( f"Body {id} is not found in this component (or subcomponents)."