diff --git a/doc/changelog.d/1721.added.md b/doc/changelog.d/1721.added.md new file mode 100644 index 0000000000..44b3792cf2 --- /dev/null +++ b/doc/changelog.d/1721.added.md @@ -0,0 +1 @@ +single design per modeler session refactoring \ No newline at end of file diff --git a/src/ansys/geometry/core/__init__.py b/src/ansys/geometry/core/__init__.py index d704dd21f2..96ec99365f 100644 --- a/src/ansys/geometry/core/__init__.py +++ b/src/ansys/geometry/core/__init__.py @@ -55,12 +55,5 @@ """Global constant for checking whether to use service colors for plotting purposes. If set to False, the default colors will be used (speed gain).""" -DISABLE_MULTIPLE_DESIGN_CHECK: bool = False -"""Global constant for disabling the ``ensure_design_is_active`` check. - -Only set this to false if you are sure you want to disable this check -and you will ONLY be working with one design. -""" - DOCUMENTATION_BUILD: bool = os.environ.get("PYANSYS_GEOMETRY_DOC_BUILD", "false").lower() == "true" """Global flag for the documentation to use the proper PyVista Jupyter backend.""" diff --git a/src/ansys/geometry/core/connection/client.py b/src/ansys/geometry/core/connection/client.py index fe7a7484e8..a4e9509b2c 100644 --- a/src/ansys/geometry/core/connection/client.py +++ b/src/ansys/geometry/core/connection/client.py @@ -29,6 +29,7 @@ import warnings from ansys.geometry.core.errors import protect_grpc +from ansys.geometry.core.misc.checks import deprecated_method # TODO: Remove this context and filter once the protobuf UserWarning issue is downgraded to INFO # https://github.com/grpc/grpc/issues/37609 @@ -224,18 +225,6 @@ def __init__( # Store the backend type self._backend_type = backend_type - self._multiple_designs_allowed = ( - False - if backend_type - in ( - BackendType.DISCOVERY, - BackendType.LINUX_SERVICE, - BackendType.CORE_LINUX, - BackendType.CORE_WINDOWS, - BackendType.DISCOVERY_HEADLESS, - ) - else True - ) # retrieve the backend version if hasattr(grpc_backend_response, "version"): @@ -277,15 +266,18 @@ def backend_version(self) -> semver.version.Version: return self._backend_version @property + @deprecated_method(info="Multiple designs for the same service are no longer supported.") def multiple_designs_allowed(self) -> bool: """Flag indicating whether multiple designs are allowed. + Deprecated since version 0.8.X. + Notes ----- - This method will return ``False`` if the backend type is ``Discovery`` or - ``Linux Service``. Otherwise, it will return ``True``. + Currently, only one design is allowed per service. This method will always + return ``False``. """ - return self._multiple_designs_allowed + return False @property def channel(self) -> grpc.Channel: diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index 59dc835a16..2d9ff06484 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -83,7 +83,6 @@ from ansys.geometry.core.misc.checks import ( check_type, check_type_all_elements_in_iterable, - ensure_design_is_active, min_backend_version, ) from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Angle, Distance @@ -1438,7 +1437,6 @@ def parent_component(self) -> "Component": # noqa: D102 @property @protect_grpc - @ensure_design_is_active def faces(self) -> list[Face]: # noqa: D102 self._template._grpc_client.log.debug(f"Retrieving faces for body {self.id} from server.") grpc_faces = self._template._bodies_stub.GetFaces(EntityIdentifier(id=self.id)) @@ -1455,7 +1453,6 @@ def faces(self) -> list[Face]: # noqa: D102 @property @protect_grpc - @ensure_design_is_active def edges(self) -> list[Edge]: # noqa: D102 self._template._grpc_client.log.debug(f"Retrieving edges for body {self.id} from server.") grpc_edges = self._template._bodies_stub.GetEdges(EntityIdentifier(id=self.id)) @@ -1511,12 +1508,10 @@ def surface_offset(self) -> Union["MidSurfaceOffsetType", None]: # noqa: D102 return self._surface_offset @property - @ensure_design_is_active def volume(self) -> Quantity: # noqa: D102 return self._template.volume @property - @ensure_design_is_active def material(self) -> Material: # noqa: D102 return self._template.material @@ -1524,26 +1519,21 @@ def material(self) -> Material: # noqa: D102 def material(self, value: Material): # noqa: D102 self._template.material = value - @ensure_design_is_active def assign_material(self, material: Material) -> None: # noqa: D102 self._template.assign_material(material) - @ensure_design_is_active def get_assigned_material(self) -> Material: # noqa: D102 return self._template.get_assigned_material() - @ensure_design_is_active def add_midsurface_thickness(self, thickness: Quantity) -> None: # noqa: D102 self._template.add_midsurface_thickness(thickness) - @ensure_design_is_active def add_midsurface_offset( # noqa: D102 self, offset: "MidSurfaceOffsetType" ) -> None: self._template.add_midsurface_offset(offset) @protect_grpc - @ensure_design_is_active def imprint_curves( # noqa: D102 self, faces: list[Face], sketch: Sketch ) -> tuple[list[Edge], list[Face]]: @@ -1595,7 +1585,6 @@ def imprint_curves( # noqa: D102 return (new_edges, new_faces) @protect_grpc - @ensure_design_is_active def project_curves( # noqa: D102 self, direction: UnitVector3D, @@ -1632,7 +1621,6 @@ def project_curves( # noqa: D102 @check_input_types @protect_grpc - @ensure_design_is_active def imprint_projected_curves( # noqa: D102 self, direction: UnitVector3D, @@ -1667,29 +1655,23 @@ def imprint_projected_curves( # noqa: D102 return imprinted_faces - @ensure_design_is_active def set_name(self, name: str) -> None: # noqa: D102 return self._template.set_name(name) - @ensure_design_is_active def set_fill_style(self, fill_style: FillStyle) -> None: # noqa: D102 return self._template.set_fill_style(fill_style) - @ensure_design_is_active def set_suppressed(self, suppressed: bool) -> None: # noqa: D102 return self._template.set_suppressed(suppressed) - @ensure_design_is_active def set_color(self, color: str | tuple[float, float, float]) -> None: # noqa: D102 return self._template.set_color(color) - @ensure_design_is_active def translate( # noqa: D102 self, direction: UnitVector3D, distance: Quantity | Distance | Real ) -> None: return self._template.translate(direction, distance) - @ensure_design_is_active def rotate( # noqa: D102 self, axis_origin: Point3D, @@ -1698,37 +1680,29 @@ def rotate( # noqa: D102 ) -> None: return self._template.rotate(axis_origin, axis_direction, angle) - @ensure_design_is_active def scale(self, value: Real) -> None: # noqa: D102 return self._template.scale(value) - @ensure_design_is_active def map(self, frame: Frame) -> None: # noqa: D102 return self._template.map(frame) - @ensure_design_is_active def mirror(self, plane: Plane) -> None: # noqa: D102 return self._template.mirror(plane) - @ensure_design_is_active def get_collision(self, body: "Body") -> CollisionType: # noqa: D102 return self._template.get_collision(body) - @ensure_design_is_active def copy(self, parent: "Component", name: str = None) -> "Body": # noqa: D102 return self._template.copy(parent, name) - @ensure_design_is_active def tessellate( # noqa: D102 self, merge: bool = False ) -> Union["PolyData", "MultiBlock"]: return self._template.tessellate(merge, self.parent_component.get_world_transform()) - @ensure_design_is_active def shell_body(self, offset: Real) -> bool: # noqa: D102 return self._template.shell_body(offset) - @ensure_design_is_active def remove_faces(self, selection: Face | Iterable[Face], offset: Real) -> bool: # noqa: D102 return self._template.remove_faces(selection, offset) @@ -1787,7 +1761,6 @@ def unite(self, other: Union["Body", Iterable["Body"]], keep_other: bool = False @protect_grpc @reset_tessellation_cache - @ensure_design_is_active @check_input_types def __generic_boolean_command( self, @@ -1840,7 +1813,6 @@ def __generic_boolean_command( @protect_grpc @reset_tessellation_cache - @ensure_design_is_active @check_input_types def __generic_boolean_op( self, diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index ad53f3967e..64988d65ad 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -75,7 +75,7 @@ from ansys.geometry.core.math.matrix import Matrix44 from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.math.vector import UnitVector3D, Vector3D -from ansys.geometry.core.misc.checks import ensure_design_is_active, min_backend_version +from ansys.geometry.core.misc.checks import min_backend_version from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Angle, Distance from ansys.geometry.core.shapes.curves.circle import Circle from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve @@ -358,7 +358,6 @@ def get_world_transform(self) -> Matrix44: return self.parent_component.get_world_transform() * self._master_component.transform @protect_grpc - @ensure_design_is_active def modify_placement( self, translation: Vector3D | None = None, @@ -416,7 +415,6 @@ def reset_placement(self): self.modify_placement() @check_input_types - @ensure_design_is_active def add_component( self, name: str, template: Optional["Component"] = None, instance_name: str = None ) -> "Component": @@ -460,7 +458,6 @@ def add_component( @protect_grpc @check_input_types - @ensure_design_is_active def set_shared_topology(self, share_type: SharedTopologyType) -> None: """Set the shared topology to apply to the component. @@ -482,7 +479,6 @@ def set_shared_topology(self, share_type: SharedTopologyType) -> None: @protect_grpc @check_input_types - @ensure_design_is_active def extrude_sketch( self, name: str, @@ -567,7 +563,6 @@ def extrude_sketch( @min_backend_version(24, 2, 0) @protect_grpc @check_input_types - @ensure_design_is_active def sweep_sketch( self, name: str, @@ -617,7 +612,6 @@ def sweep_sketch( @min_backend_version(24, 2, 0) @protect_grpc @check_input_types - @ensure_design_is_active def sweep_chain( self, name: str, @@ -717,7 +711,6 @@ def revolve_sketch( @protect_grpc @check_input_types - @ensure_design_is_active def extrude_face( self, name: str, @@ -780,7 +773,6 @@ def extrude_face( @protect_grpc @check_input_types - @ensure_design_is_active @min_backend_version(24, 2, 0) def create_sphere(self, name: str, center: Point3D, radius: Distance) -> Body: """Create a sphere body defined by the center point and the radius. @@ -814,7 +806,6 @@ def create_sphere(self, name: str, center: Point3D, radius: Distance) -> Body: @protect_grpc @check_input_types - @ensure_design_is_active @min_backend_version(24, 2, 0) def create_body_from_loft_profile( self, @@ -881,7 +872,6 @@ def create_body_from_loft_profile( @protect_grpc @check_input_types - @ensure_design_is_active def create_surface(self, name: str, sketch: Sketch) -> Body: """Create a surface body with a sketch profile. @@ -919,7 +909,6 @@ def create_surface(self, name: str, sketch: Sketch) -> Body: @protect_grpc @check_input_types - @ensure_design_is_active def create_surface_from_face(self, name: str, face: Face) -> Body: """Create a surface body based on a face. @@ -960,7 +949,6 @@ def create_surface_from_face(self, name: str, face: Face) -> Body: @protect_grpc @check_input_types - @ensure_design_is_active @min_backend_version(25, 1, 0) def create_body_from_surface(self, name: str, trimmed_surface: TrimmedSurface) -> Body: """Create a surface body from a trimmed surface. @@ -1036,7 +1024,6 @@ def create_surface_from_trimmed_curves( return Body(response.id, response.name, self, tb) @check_input_types - @ensure_design_is_active def create_coordinate_system(self, name: str, frame: Frame) -> CoordinateSystem: """Create a coordinate system. @@ -1059,7 +1046,6 @@ def create_coordinate_system(self, name: str, frame: Frame) -> CoordinateSystem: @protect_grpc @check_input_types - @ensure_design_is_active def translate_bodies( self, bodies: list[Body], direction: UnitVector3D, distance: Quantity | Distance | Real ) -> None: @@ -1111,7 +1097,6 @@ def translate_bodies( @protect_grpc @check_input_types - @ensure_design_is_active def create_beams( self, segments: list[tuple[Point3D, Point3D]], profile: BeamProfile ) -> list[Beam]: @@ -1172,7 +1157,6 @@ def create_beam(self, start: Point3D, end: Point3D, profile: BeamProfile) -> Bea @protect_grpc @check_input_types - @ensure_design_is_active def delete_component(self, component: Union["Component", str]) -> None: """Delete a component (itself or its children). @@ -1207,7 +1191,6 @@ def delete_component(self, component: Union["Component", str]) -> None: @protect_grpc @check_input_types - @ensure_design_is_active def delete_body(self, body: Body | str) -> None: """Delete a body belonging to this component (or its children). @@ -1259,7 +1242,6 @@ def add_design_point( @protect_grpc @check_input_types - @ensure_design_is_active def add_design_points( self, name: str, @@ -1295,7 +1277,6 @@ def add_design_points( @protect_grpc @check_input_types - @ensure_design_is_active def delete_beam(self, beam: Beam | str) -> None: """Delete an existing beam belonging to this component's scope. diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 32f16b6312..5ea669f45f 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -78,7 +78,7 @@ from ansys.geometry.core.math.plane import Plane from ansys.geometry.core.math.point import Point3D from ansys.geometry.core.math.vector import UnitVector3D, Vector3D -from ansys.geometry.core.misc.checks import ensure_design_is_active, min_backend_version +from ansys.geometry.core.misc.checks import min_backend_version from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Distance from ansys.geometry.core.modeler import Modeler from ansys.geometry.core.parameters.parameter import Parameter, ParameterUpdateStatus @@ -212,10 +212,6 @@ def close(self) -> None: @protect_grpc def _activate(self, called_after_design_creation: bool = False) -> None: """Activate the design.""" - # Deactivate all designs first - for design in self._modeler._designs.values(): - design._is_active = False - # Activate the current design if not called_after_design_creation: self._design_stub.PutActive(EntityIdentifier(id=self._design_id)) @@ -226,7 +222,6 @@ def _activate(self, called_after_design_creation: bool = False) -> None: # https://github.com/ansys/pyansys-geometry/issues/1319 @protect_grpc @check_input_types - @ensure_design_is_active def add_material(self, material: Material) -> None: """Add a material to the design. @@ -259,7 +254,6 @@ def add_material(self, material: Material) -> None: @protect_grpc @check_input_types - @ensure_design_is_active def save(self, file_location: Path | str) -> None: """Save a design to disk on the active Geometry server instance. @@ -277,7 +271,6 @@ def save(self, file_location: Path | str) -> None: @protect_grpc @check_input_types - @ensure_design_is_active def download( self, file_location: Path | str, @@ -640,7 +633,6 @@ def export_to_pmdb(self, location: Path | str | None = None) -> Path: return file_location @check_input_types - @ensure_design_is_active def create_named_selection( self, name: str, @@ -691,7 +683,6 @@ def create_named_selection( @protect_grpc @check_input_types - @ensure_design_is_active def delete_named_selection(self, named_selection: NamedSelection | str) -> None: """Delete a named selection on the active Geometry server instance. @@ -722,7 +713,6 @@ def delete_named_selection(self, named_selection: NamedSelection | str) -> None: pass @check_input_types - @ensure_design_is_active def delete_component(self, component: Union["Component", str]) -> None: """Delete a component (itself or its children). @@ -764,7 +754,6 @@ def set_shared_topology(self, share_type: SharedTopologyType) -> None: @protect_grpc @check_input_types - @ensure_design_is_active def add_beam_circular_profile( self, name: str, @@ -858,7 +847,6 @@ def set_parameter(self, dimension: Parameter) -> ParameterUpdateStatus: @protect_grpc @check_input_types - @ensure_design_is_active def add_midsurface_thickness(self, thickness: Quantity, bodies: list[Body]) -> None: """Add a mid-surface thickness to a list of bodies. @@ -898,7 +886,6 @@ def add_midsurface_thickness(self, thickness: Quantity, bodies: list[Body]) -> N @protect_grpc @check_input_types - @ensure_design_is_active def add_midsurface_offset(self, offset_type: MidSurfaceOffsetType, bodies: list[Body]) -> None: """Add a mid-surface offset type to a list of bodies. @@ -936,7 +923,6 @@ def add_midsurface_offset(self, offset_type: MidSurfaceOffsetType, bodies: list[ @protect_grpc @check_input_types - @ensure_design_is_active def delete_beam_profile(self, beam_profile: BeamProfile | str) -> None: """Remove a beam profile on the active geometry server instance. @@ -961,7 +947,6 @@ def delete_beam_profile(self, beam_profile: BeamProfile | str) -> None: @protect_grpc @check_input_types - @ensure_design_is_active @min_backend_version(24, 2, 0) def insert_file(self, file_location: Path | str) -> Component: """Insert a file into the design. @@ -1199,13 +1184,5 @@ def _update_design_inplace(self) -> None: self._named_selections = {} self._coordinate_systems = {} - # Get the previous design id - previous_design_id = self._design_id - # Read the existing design self.__read_existing_design() - - # If the design id has changed, update the design id in the Modeler - if previous_design_id != self._design_id: - self._modeler._designs[self._design_id] = self - self._modeler._designs.pop(previous_design_id) diff --git a/src/ansys/geometry/core/designer/edge.py b/src/ansys/geometry/core/designer/edge.py index 3b67b9c06d..9e09f1483e 100644 --- a/src/ansys/geometry/core/designer/edge.py +++ b/src/ansys/geometry/core/designer/edge.py @@ -32,7 +32,7 @@ from ansys.geometry.core.connection.conversions import grpc_curve_to_curve from ansys.geometry.core.errors import GeometryRuntimeError, protect_grpc from ansys.geometry.core.math.point import Point3D -from ansys.geometry.core.misc.checks import ensure_design_is_active, min_backend_version +from ansys.geometry.core.misc.checks import min_backend_version from ansys.geometry.core.misc.measurements import DEFAULT_UNITS from ansys.geometry.core.shapes.curves.trimmed_curve import ReversedTrimmedCurve, TrimmedCurve from ansys.geometry.core.shapes.parameterization import Interval @@ -112,7 +112,6 @@ def is_reversed(self) -> bool: @property @protect_grpc - @ensure_design_is_active @min_backend_version(24, 2, 0) def shape(self) -> TrimmedCurve: """Underlying trimmed curve of the edge. @@ -145,7 +144,6 @@ def shape(self) -> TrimmedCurve: @property @protect_grpc - @ensure_design_is_active def length(self) -> Quantity: """Calculated length of the edge.""" try: @@ -163,7 +161,6 @@ def curve_type(self) -> CurveType: @property @protect_grpc - @ensure_design_is_active def faces(self) -> list["Face"]: """Faces that contain the edge.""" from ansys.geometry.core.designer.face import Face, SurfaceType @@ -183,7 +180,6 @@ def faces(self) -> list["Face"]: @property @protect_grpc - @ensure_design_is_active def start(self) -> Point3D: """Start point of the edge.""" try: @@ -196,7 +192,6 @@ def start(self) -> Point3D: @property @protect_grpc - @ensure_design_is_active def end(self) -> Point3D: """End point of the edge.""" try: diff --git a/src/ansys/geometry/core/designer/face.py b/src/ansys/geometry/core/designer/face.py index b437a49123..6bf50fbc5f 100644 --- a/src/ansys/geometry/core/designer/face.py +++ b/src/ansys/geometry/core/designer/face.py @@ -45,7 +45,6 @@ from ansys.geometry.core.math.vector import UnitVector3D from ansys.geometry.core.misc.checks import ( deprecated_method, - ensure_design_is_active, min_backend_version, ) from ansys.geometry.core.misc.measurements import DEFAULT_UNITS @@ -204,7 +203,6 @@ def body(self) -> "Body": @property @protect_grpc - @ensure_design_is_active @min_backend_version(24, 2, 0) def shape(self) -> TrimmedSurface: """Underlying trimmed surface of the face. @@ -234,7 +232,6 @@ def surface_type(self) -> SurfaceType: @property @protect_grpc - @ensure_design_is_active def area(self) -> Quantity: """Calculated area of the face.""" self._grpc_client.log.debug("Requesting face area from server.") @@ -243,7 +240,6 @@ def area(self) -> Quantity: @property @protect_grpc - @ensure_design_is_active def edges(self) -> list[Edge]: """List of all edges of the face.""" self._grpc_client.log.debug("Requesting face edges from server.") @@ -252,7 +248,6 @@ def edges(self) -> list[Edge]: @property @protect_grpc - @ensure_design_is_active def loops(self) -> list[FaceLoop]: """List of all loops of the face.""" self._grpc_client.log.debug("Requesting face loops from server.") @@ -288,7 +283,6 @@ def loops(self) -> list[FaceLoop]: return loops @protect_grpc - @ensure_design_is_active def normal(self, u: float = 0.5, v: float = 0.5) -> UnitVector3D: """Get the normal direction to the face at certain UV coordinates. @@ -349,7 +343,6 @@ def face_normal(self, u: float = 0.5, v: float = 0.5) -> UnitVector3D: # [depre return self.normal(u, v) @protect_grpc - @ensure_design_is_active def point(self, u: float = 0.5, v: float = 0.5) -> Point3D: """Get a point of the face evaluated at certain UV coordinates. @@ -434,7 +427,6 @@ def __grpc_edges_to_edges(self, edges_grpc: list[GRPCEdge]) -> list[Edge]: return edges @protect_grpc - @ensure_design_is_active def create_isoparametric_curves( self, use_u_param: bool, parameter: float ) -> list[TrimmedCurve]: diff --git a/src/ansys/geometry/core/misc/checks.py b/src/ansys/geometry/core/misc/checks.py index b5bc5f6bc9..42ab7d8289 100644 --- a/src/ansys/geometry/core/misc/checks.py +++ b/src/ansys/geometry/core/misc/checks.py @@ -30,64 +30,7 @@ import semver if TYPE_CHECKING: # pragma: no cover - from ansys.geometry.core.designer.design import Design - - -def ensure_design_is_active(method): - """Make sure that the design is active before executing a method. - - This function is necessary to be called whenever we do any operation - on the design. If we are just accessing information of the class, it - is not necessary to call this. - """ - - def wrapper(self, *args, **kwargs): - import ansys.geometry.core as pyansys_geometry - from ansys.geometry.core.errors import GeometryRuntimeError - - if pyansys_geometry.DISABLE_MULTIPLE_DESIGN_CHECK: - # If the user has disabled the check, then we can skip it - return method(self, *args, **kwargs) - - # Check if the current design is active... otherwise activate it - def get_design_ref(obj) -> "Design": - if hasattr(obj, "_modeler"): # In case of a Design object - return obj - elif hasattr( - obj, "_parent_component" - ): # In case of a Body, Component, DesignPoint, Beam - # Recursive call - return get_design_ref(obj._parent_component) - elif hasattr(obj, "_body"): # In case of a Face, Edge - # Recursive call - return get_design_ref(obj._body._parent_component) - else: - raise ValueError("Unable to find the design reference.") - - # Get the design reference - design = get_design_ref(self) - - # Verify whether the Design has been closed on the backend - if design.is_closed: - raise GeometryRuntimeError( - "The design has been closed on the backend. Cannot perform any operations on it." - ) - - # Activate the design if it is not active - if not design.is_active: - # First, check the backend allows for multiple documents - if not design._grpc_client.multiple_designs_allowed: - raise GeometryRuntimeError( - "The design is not active and multiple designs are " - "not allowed with the current backend." - ) - else: - design._activate() - - # Finally, call method - return method(self, *args, **kwargs) - - return wrapper + pass def check_is_float_int(param: object, param_name: str | None = None) -> None: diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index 288f4abf97..4e9fa5f8ec 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -37,8 +37,7 @@ from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.connection.defaults import DEFAULT_HOST, DEFAULT_PORT from ansys.geometry.core.errors import GeometryRuntimeError, protect_grpc -from ansys.geometry.core.logger import LOG -from ansys.geometry.core.misc.checks import check_type, min_backend_version +from ansys.geometry.core.misc.checks import check_type, deprecated_method, min_backend_version from ansys.geometry.core.misc.options import ImportOptions from ansys.geometry.core.tools.measurement_tools import MeasurementTools from ansys.geometry.core.tools.prepare_tools import PrepareTools @@ -120,15 +119,15 @@ def __init__( backend_type=backend_type, ) - # Maintaining references to all designs within the modeler workspace - self._designs: dict[str, "Design"] = {} + # Single design for the Modeler + self._design: Optional["Design"] = None # Initialize the RepairTools - Not available on Linux # TODO: delete "if" when Linux service is able to use repair tools # https://github.com/ansys/pyansys-geometry/issues/1319 if BackendType.is_core_service(self.client.backend_type): self._measurement_tools = None - LOG.warning("CoreService backend does not support measurement tools.") + self.client.log.warning("CoreService backend does not support measurement tools.") else: self._measurement_tools = MeasurementTools(self._grpc_client) @@ -138,28 +137,26 @@ def __init__( self._geometry_commands = GeometryCommands(self._grpc_client) self._unsupported = UnsupportedCommands(self._grpc_client, self) - # Check if the backend allows for multiple designs and throw warning if needed - if not self.client.multiple_designs_allowed: - LOG.warning( - "Linux and Ansys Discovery backends do not support multiple " - "designs open in the same session. Only the last design created " - "will be available to perform modeling operations." - ) - @property def client(self) -> GrpcClient: """``Modeler`` instance client.""" return self._grpc_client @property + def design(self) -> "Design": + """Retrieve the design within the modeler workspace.""" + return self._design + + @property + @deprecated_method(alternative="design") def designs(self) -> dict[str, "Design"]: - """All designs within the modeler workspace. + """Retrieve the design within the modeler workspace. Notes ----- - This property is read-only. **DO NOT** modify the dictionary. + This method is deprecated. Use the :func:`design` property instead. """ - return self._designs + return {self._design.id: self._design} def create_design(self, name: str) -> "Design": """Initialize a new design with the connected client. @@ -177,14 +174,20 @@ def create_design(self, name: str) -> "Design": from ansys.geometry.core.designer.design import Design check_type(name, str) + + # If a previous design was available... inform the user that it will be deleted + # when creating a new design. + if self._design is not None and self._design.is_active: + self.client.log.warning("Closing previous design before creating a new one.") + self._design.close() + + # Create the new design design = Design(name, self) - self._designs[design.design_id] = design - if len(self._designs) > 1: - LOG.warning( - "Some backends only support one design. " - + "Previous designs may be deleted (on the service) when creating a new one." - ) - return self._designs[design.design_id] + + # Update the design stored in the modeler + self._design = design + + return self._design def get_active_design(self, sync_with_backend: bool = True) -> "Design": """Get the active design on the modeler object. @@ -201,14 +204,13 @@ def get_active_design(self, sync_with_backend: bool = True) -> "Design": Design Design object already existing on the modeler. """ - for _, design in self._designs.items(): - if design._is_active: - # Check if sync_with_backend is requested - if sync_with_backend: - design._update_design_inplace() - - # Return the active design - return design + if self._design is not None and self._design.is_active: + # Check if sync_with_backend is requested + if sync_with_backend: + self._design._update_design_inplace() + return self._design + else: + self.client.log.warning("No active design available.") return None @@ -222,43 +224,39 @@ def read_existing_design(self) -> "Design": """ from ansys.geometry.core.designer.design import Design - design = Design("", self, read_existing_design=True) - self._designs[design.design_id] = design - if len(self._designs) > 1: - LOG.warning( - "Some backends only support one design. " - + "Previous designs may be deleted (on the service) when reading a new one." - ) - return self._designs[design.design_id] + self._design = Design("", self, read_existing_design=True) + + return self._design - def close(self, close_designs: bool = True) -> None: + def close(self, close_design: bool = True) -> None: """Access the client's close method. Parameters ---------- - close_designs : bool, default: True - Whether to close all designs before closing the client. + close_design : bool, default: True + Whether to close the design before closing the client. """ - # Close all designs (if requested) - [design.close() for design in self._designs.values() if close_designs] + # Close design (if requested) + if close_design and self._design is not None: + self._design.close() # Close the client self.client.close() - def exit(self, close_designs: bool = True) -> None: + def exit(self, close_design: bool = True) -> None: """Access the client's close method. Parameters ---------- - close_designs : bool, default: True - Whether to close all designs before closing the client. + close_design : bool, default: True + Whether to close the design before closing the client. Notes ----- This method is calling the same method as :func:`close() `. """ - self.close(close_designs=close_designs) + self.close(close_design=close_design) def _upload_file( self, @@ -301,7 +299,7 @@ def _upload_file( with fp_path.open(mode="rb") as file: data = file.read() - c_stub = CommandsStub(self._grpc_client.channel) + c_stub = CommandsStub(self.client.channel) response = c_stub.UploadFile( UploadFileRequest( @@ -361,7 +359,7 @@ def open_file( self._upload_file(full_path) self._upload_file(file_path, True, import_options) else: - DesignsStub(self._grpc_client.channel).Open( + DesignsStub(self.client.channel).Open( OpenRequest(filepath=file_path, import_options=import_options.to_dict()) ) @@ -372,7 +370,7 @@ def __repr__(self) -> str: lines = [] lines.append(f"Ansys Geometry Modeler ({hex(id(self))})") lines.append("") - lines.append(str(self._grpc_client)) + lines.append(str(self.client)) return "\n".join(lines) @protect_grpc @@ -468,7 +466,7 @@ def run_discovery_script_file( api_version = ApiVersions.parse_input(api_version) serv_path = self._upload_file(file_path) - ga_stub = DbuApplicationStub(self._grpc_client.channel) + ga_stub = DbuApplicationStub(self.client.channel) request = RunScriptFileRequest( script_path=serv_path, script_args=script_args, diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 102a53851a..69f4634a85 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -162,10 +162,7 @@ def modeler(session_modeler: Modeler): yield session_modeler # Cleanup on exit - [design.close() for design in session_modeler.designs.values()] - - # Empty the designs dictionary - session_modeler._designs = {} + session_modeler.design.close() @pytest.fixture(scope="session", autouse=True) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 51d2281f68..af1f85d30a 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -1986,18 +1986,13 @@ def test_child_component_instances(modeler: Modeler): def test_multiple_designs(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): - """Generate multiple designs, make sure they are all separate, and activate - them when needed. + """Generate multiple designs, make sure they are all separate, and once + a design is deactivated, the next one is activated. """ - # Check backend first - if modeler.client.backend_type in ( - BackendType.SPACECLAIM, - BackendType.WINDOWS_SERVICE, - ): - pass - else: - # Test is only available for DMS and SpaceClaim - pytest.skip("Test only available on DMS and SpaceClaim") + # Initiate expected output images + scshot_dir = tmp_path_factory.mktemp("test_multiple_designs") + scshot_1 = scshot_dir / "design1.png" + scshot_2 = scshot_dir / "design2.png" # Create your design on the server side design1 = modeler.create_design("Design1") @@ -2009,6 +2004,9 @@ def test_multiple_designs(modeler: Modeler, tmp_path_factory: pytest.TempPathFac # Extrude the sketch to create a body design1.extrude_sketch("MySlot", sketch1, Quantity(10, UNITS.mm)) + # Request plotting and store images + design1.plot(screenshot=scshot_1) + # Create a second design design2 = modeler.create_design("Design2") @@ -2019,14 +2017,8 @@ def test_multiple_designs(modeler: Modeler, tmp_path_factory: pytest.TempPathFac # Extrude the sketch to create a body design2.extrude_sketch("MyRectangle", sketch2, Quantity(10, UNITS.mm)) - # Initiate expected output images - scshot_dir = tmp_path_factory.mktemp("test_multiple_designs") - scshot_1 = scshot_dir / "design1.png" - scshot_2 = scshot_dir / "design2.png" - # Request plotting and store images - design2.plot(screenshot=scshot_1) - design1.plot(screenshot=scshot_2) + design2.plot(screenshot=scshot_2) # Check that the images are different assert scshot_1.exists() @@ -2034,13 +2026,9 @@ def test_multiple_designs(modeler: Modeler, tmp_path_factory: pytest.TempPathFac err = pv_compare_images(str(scshot_1), str(scshot_2)) assert not err < 0.1 - # Check that design2 is not active - assert not design2.is_active - assert design1.is_active - - # Check the same thing inside the modeler - assert not modeler.designs[design2.design_id].is_active - assert modeler.designs[design1.design_id].is_active + # Check that design1 is not active and design2 is active + assert not design1.is_active + assert design2.is_active def test_get_active_design(modeler: Modeler):