From 48b7bea5cb1667c7763c55cb779a60d45a91594c Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:09:29 +0100 Subject: [PATCH 01/36] Test template for reading design --- tests/integration/test_design_import.py | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/integration/test_design_import.py diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py new file mode 100644 index 0000000000..33172b1368 --- /dev/null +++ b/tests/integration/test_design_import.py @@ -0,0 +1,59 @@ +"""Test design import.""" + +from pint import Quantity + +from ansys.geometry.core import Modeler +from ansys.geometry.core.math import Point2D +from ansys.geometry.core.misc import UNITS +from ansys.geometry.core.sketch import Sketch + + +def test_design_import_simple_case(modeler: Modeler): + # With the given session let's create a the following Design + # + # Create your design on the server side + design = modeler.create_design("ReadDesign") + + # Create a Sketch object and draw a circle (all client side) + sketch = Sketch() + sketch.circle(Point2D([-30, -30], UNITS.mm), Quantity(10, UNITS.mm)) + distance = Quantity(30, UNITS.mm) + + # The following component hierarchy is made + # + # |---> comp_1 ---|---> nested_1_comp_1 ---> nested_1_nested_1_comp_1 + # | | + # | |---> nested_2_comp_1 + # | + # DESIGN ---|---> comp_2 -------> nested_1_comp_2 + # | + # | + # |---> comp_3 + # + # + # Now, only "comp_3", "nested_2_comp_1" and "nested_1_nested_1_comp_1" + # will have a body associated... + # + # + + # Create the components + comp_1 = design.add_component("Component_1") + comp_2 = design.add_component("Component_2") + comp_3 = design.add_component("Component_3") + nested_1_comp_1 = comp_1.add_component("Nested_1_Component_1") + nested_1_nested_1_comp_1 = nested_1_comp_1.add_component("Nested_1_Nested_1_Component_1") + nested_2_comp_1 = comp_1.add_component("Nested_2_Component_1") + comp_2.add_component("Nested_1_Component_2") + + # Create the bodies + comp_3.extrude_sketch(name="comp_3_circle", sketch=sketch, distance=distance) + nested_2_comp_1.extrude_sketch(name="nested_2_comp_1_circle", sketch=sketch, distance=distance) + nested_1_nested_1_comp_1.extrude_sketch( + name="nested_1_nested_1_comp_1_circle", sketch=sketch, distance=distance + ) + + # Now, let's create a new client session + new_client = Modeler() + # TODO: read_design = new_client.read_existing_design() + # + # TODO: And now assert all its elements From cbec5d0139f650a856d01fbc64cf5f4958268cc4 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 9 Feb 2023 15:23:40 +0100 Subject: [PATCH 02/36] Add skeleton methods --- src/ansys/geometry/core/designer/design.py | 17 +++++++++++++++++ src/ansys/geometry/core/modeler.py | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 8281581c85..6b32c2a3a8 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -455,3 +455,20 @@ def __repr__(self): lines.append(f" N Materials : {len(self.materials)}") lines.append(f" N Beam Profiles : {len(self.beam_profiles)}") return "\n".join(lines) + + @classmethod + def read_existing_design(cls, grpc_client: GrpcClient) -> "Design": + """Read existing design on the service with the connected client. + + Parameters + ---------- + grpc_client : GrpcClient + Active supporting Geometry service instance for design modeling. + + Returns + ------- + Design + Design object already living on the server. + """ + # TODO: To be implemented... + return None diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index beaaadb7c8..32a67ef7ac 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -99,6 +99,20 @@ def create_design(self, name: str) -> "Design": self._designs.append(design) return self._designs[-1] + def read_existing_design(self) -> "Design": + """Read existing design on the service with the connected client. + + Returns + ------- + Design + Design object already living on the server. + """ + from ansys.geometry.core.designer.design import Design + + design = Design.read_existing_design(self._client) + self._designs.append(design) + return self._designs[-1] + def close(self) -> None: """``Modeler`` easy-access method to the client's ``close()`` method.""" return self.client.close() From 2c8d78c466fa7f2c816115e662ba2fe77f29f22d Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 9 Feb 2023 15:25:24 +0100 Subject: [PATCH 03/36] Activate testing (even though empty method) --- tests/integration/test_design_import.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 33172b1368..ec04dc8014 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -54,6 +54,8 @@ def test_design_import_simple_case(modeler: Modeler): # Now, let's create a new client session new_client = Modeler() - # TODO: read_design = new_client.read_existing_design() + read_design = new_client.read_existing_design() # # TODO: And now assert all its elements + assert read_design is None + assert len(new_client._designs) == 1 From a04e0324bab38c4219c2f889b1f14598f0376f3b Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:05:23 +0100 Subject: [PATCH 04/36] Improvements on instantiation of Design read --- src/ansys/geometry/core/designer/component.py | 4 ++ src/ansys/geometry/core/designer/design.py | 49 ++++++++++++------- src/ansys/geometry/core/modeler.py | 2 +- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 61a42b61cd..a2b1918988 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -76,11 +76,14 @@ class Component: @check_input_types def __init__(self, name: str, parent_component: Optional["Component"], grpc_client: GrpcClient): """Constructor method for the ``Component`` class.""" + + # Initialize the client and stubs needed self._grpc_client = grpc_client self._component_stub = ComponentsStub(self._grpc_client.channel) self._bodies_stub = BodiesStub(self._grpc_client.channel) self._commands_stub = CommandsStub(self._grpc_client.channel) + # Check if we are dealing with the root component (i.e. parent_component is None) if parent_component: new_component = self._component_stub.Create( CreateRequest(name=name, parent=parent_component.id) @@ -91,6 +94,7 @@ def __init__(self, name: str, parent_component: Optional["Component"], grpc_clie self._name = name self._id = None + # Initialize needed instance variables self._components = [] self._bodies = [] self._beams = [] diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 6b32c2a3a8..597a6a7ff1 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -68,6 +68,11 @@ class Design(Component): User-defined label for the design. grpc_client : GrpcClient Active supporting Geometry service instance for design modeling. + read_existing_design : bool, optional + Indicates whether an existing design on the service should be read + or not. By default, ``False``. This is only valid when connecting + to an existing service session. Otherwise, avoid using this optional + argument. """ # Types of the class instance private attributes @@ -77,23 +82,29 @@ class Design(Component): @protect_grpc @check_input_types - def __init__(self, name: str, grpc_client: GrpcClient): + def __init__(self, name: str, grpc_client: GrpcClient, read_existing_design: bool = False): """Constructor method for the ``Design`` class.""" super().__init__(name, None, grpc_client) + # Initialize the stubs needed self._design_stub = DesignsStub(self._grpc_client.channel) self._commands_stub = CommandsStub(self._grpc_client.channel) self._materials_stub = MaterialsStub(self._grpc_client.channel) self._named_selections_stub = NamedSelectionsStub(self._grpc_client.channel) - new_design = self._design_stub.New(NewRequest(name=name)) - self._id = new_design.id - + # Initialize needed instance variables self._materials = [] self._named_selections = {} self._beam_profiles = {} - self._grpc_client.log.debug("Design object instantiated successfully.") + # Check whether we want to process an existing design or create a new one. + if read_existing_design: + self._grpc_client.log.debug("Reading Design object from service.") + self.__read_existing_design() + else: + new_design = self._design_stub.New(NewRequest(name=name)) + self._id = new_design.id + self._grpc_client.log.debug("Design object instantiated successfully.") @property def materials(self) -> List[Material]: @@ -456,19 +467,21 @@ def __repr__(self): lines.append(f" N Beam Profiles : {len(self.beam_profiles)}") return "\n".join(lines) - @classmethod - def read_existing_design(cls, grpc_client: GrpcClient) -> "Design": + def __read_existing_design(self) -> None: """Read existing design on the service with the connected client. - - Parameters - ---------- - grpc_client : GrpcClient - Active supporting Geometry service instance for design modeling. - - Returns - ------- - Design - Design object already living on the server. + This method will just fill the ``Design`` object with all its + existing ``Component`` and ``Body`` objects. """ # TODO: To be implemented... - return None + # + # We have to get back: + # + # - [ ] Components + # - [ ] Bodies + # - [ ] Materials + # - [ ] NamedSelections + # - [ ] BeamProfiles + # - [ ] Beams + # - [ ] CoordinateSystems + # - [ ] SharedTopology + pass diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index 32a67ef7ac..1d91005100 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -109,7 +109,7 @@ def read_existing_design(self) -> "Design": """ from ansys.geometry.core.designer.design import Design - design = Design.read_existing_design(self._client) + design = Design("", self._client, read_existing_design=True) self._designs.append(design) return self._designs[-1] From 11643c013d5e1085520575b78f3758c2ea52d533 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 9 Feb 2023 18:20:54 +0100 Subject: [PATCH 05/36] WIP: reading through design --- src/ansys/geometry/core/designer/component.py | 40 +++++++++++++++++-- src/ansys/geometry/core/designer/design.py | 13 +++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index a2b1918988..e1b6aefea8 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -13,7 +13,11 @@ from ansys.api.geometry.v0.bodies_pb2_grpc import BodiesStub from ansys.api.geometry.v0.commands_pb2 import CreateBeamSegmentsRequest from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub -from ansys.api.geometry.v0.components_pb2 import CreateRequest, SetSharedTopologyRequest +from ansys.api.geometry.v0.components_pb2 import ( + CreateRequest, + GetAllRequest, + SetSharedTopologyRequest, +) from ansys.api.geometry.v0.components_pb2_grpc import ComponentsStub from ansys.api.geometry.v0.models_pb2 import EntityIdentifier, Line from beartype import beartype as check_input_types @@ -64,6 +68,11 @@ class Component: Parent component to nest the new component under within the design assembly. grpc_client : GrpcClient Active supporting Geometry service instance for design modeling. + read_existing_comp : bool, optional + Indicates whether an existing component on the service should be read + or not. By default, ``False``. This is only valid when connecting + to an existing service session. Otherwise, avoid using this optional + argument. """ # Types of the class instance private attributes @@ -74,7 +83,13 @@ class Component: @protect_grpc @check_input_types - def __init__(self, name: str, parent_component: Optional["Component"], grpc_client: GrpcClient): + def __init__( + self, + name: str, + parent_component: Optional["Component"], + grpc_client: GrpcClient, + read_existing_comp: bool = False, + ): """Constructor method for the ``Component`` class.""" # Initialize the client and stubs needed @@ -84,7 +99,7 @@ def __init__(self, name: str, parent_component: Optional["Component"], grpc_clie self._commands_stub = CommandsStub(self._grpc_client.channel) # Check if we are dealing with the root component (i.e. parent_component is None) - if parent_component: + if parent_component and not read_existing_comp: new_component = self._component_stub.Create( CreateRequest(name=name, parent=parent_component.id) ) @@ -801,3 +816,22 @@ def __repr__(self) -> str: lines.append(f" N Components : {sum(alive_comps)}") lines.append(f" N Coordinate Systems : {len(self.coordinate_systems)}") return "\n".join(lines) + + def __read_existing_component(self, component: "Component") -> None: + # Given the existing component... + # + # Let's read the existing bodies first + list_bodies_grpc = self._component_stub.GetBodies(EntityIdentifier(id=self.id)) + for body_grpc in list_bodies_grpc: + self._bodies.append(Body(body_grpc.id, body_grpc.name, self, self._grpc_client)) + + # Now, once all bodies have been read, let's get all subcomponents + list_subcomponents_grpc = self._component_stub.GetAll(GetAllRequest(parent=self.id)) + for subcomp_grpc in list_subcomponents_grpc: + subcomp = Component("", component, self._grpc_client, read_existing_comp=True) + subcomp._id = subcomp_grpc.id + subcomp._name = subcomp_grpc.name + self._components.append(subcomp) + self.__read_existing_component(subcomp) + + # TODO: WIP... diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 597a6a7ff1..4f0fe2f1d7 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -484,4 +484,15 @@ def __read_existing_design(self) -> None: # - [ ] Beams # - [ ] CoordinateSystems # - [ ] SharedTopology - pass + + # First, let's start by getting the design object (i.e. root component) + design = self._design_stub.GetActive(Empty()) + if not design: + raise RuntimeError("No existing design available at service level.") + else: + self._id = design.id + self._name = design.name + + # Now that we have verified that there is an active design + # on the service, let's keep reading it. + self.__read_existing_component(self) From b47c998d495e16153391a8a641d09374738e2597 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Fri, 10 Feb 2023 09:08:50 +0100 Subject: [PATCH 06/36] WIP - failing tests --- src/ansys/geometry/core/designer/component.py | 1 + src/ansys/geometry/core/designer/design.py | 6 +++--- tests/integration/test_design_import.py | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index e1b6aefea8..1f86651fe3 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -821,6 +821,7 @@ def __read_existing_component(self, component: "Component") -> None: # Given the existing component... # # Let's read the existing bodies first + # if self.parent_component is not None: list_bodies_grpc = self._component_stub.GetBodies(EntityIdentifier(id=self.id)) for body_grpc in list_bodies_grpc: self._bodies.append(Body(body_grpc.id, body_grpc.name, self, self._grpc_client)) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 4f0fe2f1d7..0edd3407b6 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -490,9 +490,9 @@ def __read_existing_design(self) -> None: if not design: raise RuntimeError("No existing design available at service level.") else: - self._id = design.id - self._name = design.name + self._id = design.main_part.id + self._name = design.main_part.name # Now that we have verified that there is an active design # on the service, let's keep reading it. - self.__read_existing_component(self) + self._Component__read_existing_component(self) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index ec04dc8014..b0894717e1 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -21,14 +21,16 @@ def test_design_import_simple_case(modeler: Modeler): # The following component hierarchy is made # - # |---> comp_1 ---|---> nested_1_comp_1 ---> nested_1_nested_1_comp_1 - # | | + # |---> comp_1 ---|---> nested_1_comp_1 ---> nested_1_nested_1_comp_1 X + # | | X BODY X # | |---> nested_2_comp_1 + # | X BODY X # | # DESIGN ---|---> comp_2 -------> nested_1_comp_2 # | # | # |---> comp_3 + # X BODY X # # # Now, only "comp_3", "nested_2_comp_1" and "nested_1_nested_1_comp_1" From 5e529ed4c7fba4100a84b2386eb7d7eadcc44d01 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 14 Feb 2023 12:34:46 -0500 Subject: [PATCH 07/36] Implement upload_file() --- src/ansys/geometry/core/modeler.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index beaaadb7c8..b09ef2fd29 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -2,6 +2,8 @@ import logging from pathlib import Path +from ansys.api.geometry.v0.commands_pb2 import UploadFileRequest +from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub from beartype.typing import TYPE_CHECKING, Optional, Union from grpc import Channel @@ -103,6 +105,28 @@ def close(self) -> None: """``Modeler`` easy-access method to the client's ``close()`` method.""" return self.client.close() + def upload_file(self, bytes: bytes, file_name: str): + """ + Upload a file from the client to the server. ``file_name`` must include the extension. + + Parameters + ---------- + bytes : bytes + The file as a byte array. + file_name : str + The name for the new file created on the server. Must include extension. + Example: "example.txt" + + Returns + ------- + file_path : str + The full path of the uploaded file on the server machine. + """ + c_stub = CommandsStub(self._client.channel) + + response = c_stub.UploadFile(UploadFileRequest(data=bytes, file_name=file_name)) + return response.file_path + def __repr__(self): """String representation of the modeler.""" lines = [] From 325ebc52d1a164378518ac0eba5c2c3ac5740f89 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 14 Feb 2023 12:49:22 -0500 Subject: [PATCH 08/36] Write test --- tests/integration/test_design.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 44da992fbf..49be14eba5 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -1,5 +1,7 @@ """Test design interaction.""" +import os + from pint import Quantity import pytest @@ -801,6 +803,25 @@ def test_download_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactor assert fmd_file.exists() +def test_upload_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): + file = tmp_path_factory.mktemp("upload_file") / "example.scdocx" + file_size = 1024 + + # Write random bytes + with open(file, "wb") as fout: + fout.write(os.urandom(file_size)) + + assert file.exists() + + # Read the file to get its bytes + with open(file, "rb") as f: + data = f.read() + + # Upload file + path_on_server = modeler.upload_file(data, "example.scdocx") + assert path_on_server is not None + + def test_slot_extrusion(modeler: Modeler): """Test the extrusion of a slot.""" # Create your design on the server side From 8b33702d5b32b103590ea7ec65cad8a409953c19 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 16 Feb 2023 13:46:49 -0500 Subject: [PATCH 09/36] read bytes of file inside method now --- src/ansys/geometry/core/modeler.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index b09ef2fd29..f8e6046c49 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -105,26 +105,37 @@ def close(self) -> None: """``Modeler`` easy-access method to the client's ``close()`` method.""" return self.client.close() - def upload_file(self, bytes: bytes, file_name: str): + def upload_file(self, file_path: str) -> str: """ - Upload a file from the client to the server. ``file_name`` must include the extension. + Upload a file from the client to the server. ``file_path`` must include the extension. + The new file created on the server will have the same name and extension. Parameters ---------- - bytes : bytes - The file as a byte array. - file_name : str - The name for the new file created on the server. Must include extension. - Example: "example.txt" + file_path : str + The path of the file. Must include extension. Returns ------- file_path : str The full path of the uploaded file on the server machine. """ + import os + + if not os.path.exists(file_path): + message = f"Could not find file: {file_path}" + raise ValueError(message) + if os.path.isdir(file_path): + raise ValueError("File path must lead to a file, not a directory.") + + file_name = os.path.split(file_path)[1] + + with open(file_path, "rb") as file: + data = file.read() + c_stub = CommandsStub(self._client.channel) - response = c_stub.UploadFile(UploadFileRequest(data=bytes, file_name=file_name)) + response = c_stub.UploadFile(UploadFileRequest(data=data, file_name=file_name)) return response.file_path def __repr__(self): From d1150efc41125629c96351f19df87c8babe164db Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 16 Feb 2023 13:51:18 -0500 Subject: [PATCH 10/36] fix test --- tests/integration/test_design.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 49be14eba5..b490d7d5e6 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -813,12 +813,8 @@ def test_upload_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory) assert file.exists() - # Read the file to get its bytes - with open(file, "rb") as f: - data = f.read() - # Upload file - path_on_server = modeler.upload_file(data, "example.scdocx") + path_on_server = modeler.upload_file(file) assert path_on_server is not None From 4755c82864c410e3b0e87fe17ed0d0f15a1ebb7a Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:06:27 +0100 Subject: [PATCH 11/36] Update src/ansys/geometry/core/modeler.py --- src/ansys/geometry/core/modeler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index f8e6046c49..f1ad3b4c2b 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -123,8 +123,7 @@ def upload_file(self, file_path: str) -> str: import os if not os.path.exists(file_path): - message = f"Could not find file: {file_path}" - raise ValueError(message) + raise ValueError(f"Could not find file: {file_path}") if os.path.isdir(file_path): raise ValueError("File path must lead to a file, not a directory.") From d7a4aca94ea6a03d256c6949aa5a163cc8296050 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 2 Mar 2023 12:05:21 +0100 Subject: [PATCH 12/36] Implementing JSON logic --- src/ansys/geometry/core/designer/component.py | 40 ++++++++++--------- src/ansys/geometry/core/designer/design.py | 26 +++++++++--- tests/integration/test_design_import.py | 27 +++++++++++-- 3 files changed, 66 insertions(+), 27 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 0b6a443752..210923526f 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -13,15 +13,11 @@ from ansys.api.geometry.v0.bodies_pb2_grpc import BodiesStub from ansys.api.geometry.v0.commands_pb2 import CreateBeamSegmentsRequest from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub -from ansys.api.geometry.v0.components_pb2 import ( - CreateRequest, - GetAllRequest, - SetSharedTopologyRequest, -) +from ansys.api.geometry.v0.components_pb2 import CreateRequest, SetSharedTopologyRequest from ansys.api.geometry.v0.components_pb2_grpc import ComponentsStub from ansys.api.geometry.v0.models_pb2 import EntityIdentifier, Line from beartype import beartype as check_input_types -from beartype.typing import TYPE_CHECKING, List, Optional, Tuple, Union +from beartype.typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union from pint import Quantity from ansys.geometry.core.connection import ( @@ -820,22 +816,30 @@ def __repr__(self) -> str: lines.append(f" N Coordinate Systems : {len(self.coordinate_systems)}") return "\n".join(lines) - def __read_existing_component(self, component: "Component") -> None: + def __read_existing_component(self, component_as_json: Dict) -> None: # Given the existing component... # # Let's read the existing bodies first - # if self.parent_component is not None: - list_bodies_grpc = self._component_stub.GetBodies(EntityIdentifier(id=self.id)) - for body_grpc in list_bodies_grpc: - self._bodies.append(Body(body_grpc.id, body_grpc.name, self, self._grpc_client)) + bodies_json = component_as_json["bodies"] + subcomps_json = component_as_json["components"] + for body in bodies_json: + self._bodies.append( + Body( + body["masterid"], + body["name"], + self, + self._grpc_client, + is_surface=(not body["isclosed"]), + ) + ) # Now, once all bodies have been read, let's get all subcomponents - list_subcomponents_grpc = self._component_stub.GetAll(GetAllRequest(parent=self.id)) - for subcomp_grpc in list_subcomponents_grpc: - subcomp = Component("", component, self._grpc_client, read_existing_comp=True) - subcomp._id = subcomp_grpc.id - subcomp._name = subcomp_grpc.name + for subcomp_json in subcomps_json: + subcomp = Component("", self, self._grpc_client, read_existing_comp=True) + subcomp._id = subcomp_json["id"] + subcomp._name = subcomp_json["name"] self._components.append(subcomp) - self.__read_existing_component(subcomp) + subcomp.__read_existing_component(subcomp_json) - # TODO: WIP... + # Finally, return... just for readability purposes + return diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 74e16fcf20..a98d9dc79e 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -9,7 +9,12 @@ CreateBeamCircularProfileRequest, ) from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub -from ansys.api.geometry.v0.designs_pb2 import ExportRequest, NewRequest, SaveAsRequest +from ansys.api.geometry.v0.designs_pb2 import ( + ExportRequest, + GetAsJsonRequest, + NewRequest, + SaveAsRequest, +) from ansys.api.geometry.v0.designs_pb2_grpc import DesignsStub from ansys.api.geometry.v0.materials_pb2 import AddToDocumentRequest from ansys.api.geometry.v0.materials_pb2_grpc import MaterialsStub @@ -472,19 +477,23 @@ def __read_existing_design(self) -> None: This method will just fill the ``Design`` object with all its existing ``Component`` and ``Body`` objects. """ + + import json + # TODO: To be implemented... # # We have to get back: # - # - [ ] Components - # - [ ] Bodies + # - [X] Components + # - [X] Bodies # - [ ] Materials # - [ ] NamedSelections # - [ ] BeamProfiles # - [ ] Beams # - [ ] CoordinateSystems # - [ ] SharedTopology - + # + # # First, let's start by getting the design object (i.e. root component) design = self._design_stub.GetActive(Empty()) if not design: @@ -494,5 +503,10 @@ def __read_existing_design(self) -> None: self._name = design.main_part.name # Now that we have verified that there is an active design - # on the service, let's keep reading it. - self._Component__read_existing_component(self) + # on the service, let's read it. + response = self._design_stub.GetAsJson(GetAsJsonRequest(id="")) + design_as_json: Dict = json.loads(response.json) + + # Having the information available as a JSON file, it is now time + # to start creating the different bodies, components... + self._Component__read_existing_component(design_as_json["design"]) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index b0894717e1..a085cc6ee1 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -3,11 +3,29 @@ from pint import Quantity from ansys.geometry.core import Modeler +from ansys.geometry.core.designer import Component from ansys.geometry.core.math import Point2D from ansys.geometry.core.misc import UNITS from ansys.geometry.core.sketch import Sketch +def _checker_method(comp: Component, comp_ref: Component) -> None: + # Check component features + assert comp.id == comp_ref.id + assert comp.name == comp_ref.name + assert len(comp.bodies) == len(comp_ref.bodies) + assert len(comp.components) == len(comp_ref.components) + + # Check bodies (if any) + for body, body_ref in zip(comp.bodies, comp_ref.bodies): + assert body.id == body_ref.id + assert body.name == body_ref.name + + # Check subcomponents + for subcomp, subcomp_ref in zip(comp.components, comp_ref.components): + _checker_method(subcomp, subcomp_ref) + + def test_design_import_simple_case(modeler: Modeler): # With the given session let's create a the following Design # @@ -57,7 +75,10 @@ def test_design_import_simple_case(modeler: Modeler): # Now, let's create a new client session new_client = Modeler() read_design = new_client.read_existing_design() - # - # TODO: And now assert all its elements - assert read_design is None + + # And now assert all its elements + assert read_design is not None assert len(new_client._designs) == 1 + + # Check the design + _checker_method(read_design, design) From 2bf71401dd1b01bdca7d2c09970ba5510155b7e1 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 2 Mar 2023 12:30:43 +0100 Subject: [PATCH 13/36] Skip test run on Linux --- tests/integration/test_design_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index a085cc6ee1..6ce01cd92c 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -26,7 +26,7 @@ def _checker_method(comp: Component, comp_ref: Component) -> None: _checker_method(subcomp, subcomp_ref) -def test_design_import_simple_case(modeler: Modeler): +def test_design_import_simple_case(modeler: Modeler, skip_not_on_linux_service): # With the given session let's create a the following Design # # Create your design on the server side From 6817decac8d834f59c4f4b936d5c056b5ef1a293 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Mon, 17 Apr 2023 11:22:15 -0400 Subject: [PATCH 14/36] fix imprint/project occurrences --- src/ansys/geometry/core/designer/body.py | 117 ++++++++++++----------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index 05b7d41931..75d06adcc8 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -512,42 +512,11 @@ def add_midsurface_offset(self, offset: MidSurfaceOffsetType) -> None: @protect_grpc @check_input_types def imprint_curves(self, faces: List[Face], sketch: Sketch) -> Tuple[List[Edge], List[Face]]: - # Verify that each of the faces provided are part of this body - body_faces = self.faces - for provided_face in faces: - is_found = False - for body_face in body_faces: - if provided_face.id == body_face.id: - is_found = True - break - if not is_found: - raise ValueError(f"Face with id {provided_face.id} is not part of this body.") - - self._grpc_client.log.debug( - f"Imprinting curves provided on {self.id} " - + f"for faces {[face.id for face in faces]}." - ) - - imprint_response = self._commands_stub.ImprintCurves( - ImprintCurvesRequest( - body=self._id, - curves=sketch_shapes_to_grpc_geometries(sketch._plane, sketch.edges, sketch.faces), - faces=[face._id for face in faces], - ) + raise NotImplementedError( + "imprint_curves is not implemented at the TemplateBody level.", + "Instead, call this method on a Body.", ) - new_edges = [ - Edge(grpc_edge.id, grpc_edge.curve_type, self, self._grpc_client) - for grpc_edge in imprint_response.edges - ] - - new_faces = [ - Face(grpc_face.id, grpc_face.surface_type, self, self._grpc_client) - for grpc_face in imprint_response.faces - ] - - return (new_edges, new_faces) - @protect_grpc @check_input_types def project_curves( @@ -557,28 +526,11 @@ def project_curves( closest_face: bool, only_one_curve: Optional[bool] = False, ) -> List[Face]: - curves = sketch_shapes_to_grpc_geometries( - sketch._plane, sketch.edges, sketch.faces, only_one_curve=only_one_curve - ) - - self._grpc_client.log.debug(f"Projecting provided curves on {self.id}.") - - project_response = self._commands_stub.ProjectCurves( - ProjectCurvesRequest( - body=self._id, - curves=curves, - direction=unit_vector_to_grpc_direction(direction), - closest_face=closest_face, - ) + raise NotImplementedError( + "project_curves is not implemented at the TemplateBody level.", + "Instead, call this method on a Body.", ) - projected_faces = [ - Face(grpc_face.id, grpc_face.surface_type, self, self._grpc_client) - for grpc_face in project_response.faces - ] - - return projected_faces - @protect_grpc @check_input_types def translate(self, direction: UnitVector3D, distance: Union[Quantity, Distance, Real]) -> None: @@ -789,7 +741,41 @@ def add_midsurface_offset(self, offset: "MidSurfaceOffsetType") -> None: self._template.add_midsurface_offset(offset) def imprint_curves(self, faces: List[Face], sketch: Sketch) -> Tuple[List[Edge], List[Face]]: - return self._template.imprint_curves(faces, sketch) + # Verify that each of the faces provided are part of this body + body_faces = self.faces + for provided_face in faces: + is_found = False + for body_face in body_faces: + if provided_face.id == body_face.id: + is_found = True + break + if not is_found: + raise ValueError(f"Face with id {provided_face.id} is not part of this body.") + + self._template._grpc_client.log.debug( + f"Imprinting curves provided on {self.id} " + + f"for faces {[face.id for face in faces]}." + ) + + imprint_response = self._template._commands_stub.ImprintCurves( + ImprintCurvesRequest( + body=self._id, + curves=sketch_shapes_to_grpc_geometries(sketch._plane, sketch.edges, sketch.faces), + faces=[face._id for face in faces], + ) + ) + + new_edges = [ + Edge(grpc_edge.id, grpc_edge.curve_type, self, self._template._grpc_client) + for grpc_edge in imprint_response.edges + ] + + new_faces = [ + Face(grpc_face.id, grpc_face.surface_type, self, self._template._grpc_client) + for grpc_face in imprint_response.faces + ] + + return (new_edges, new_faces) def project_curves( self, @@ -798,7 +784,26 @@ def project_curves( closest_face: bool, only_one_curve: Optional[bool] = False, ) -> List[Face]: - return self._template.project_curves(direction, sketch, closest_face, only_one_curve) + curves = sketch_shapes_to_grpc_geometries( + sketch._plane, sketch.edges, sketch.faces, only_one_curve=only_one_curve + ) + self._template._grpc_client.log.debug(f"Projecting provided curves on {self.id}.") + + project_response = self._template._commands_stub.ProjectCurves( + ProjectCurvesRequest( + body=self._id, + curves=curves, + direction=unit_vector_to_grpc_direction(direction), + closest_face=closest_face, + ) + ) + + projected_faces = [ + Face(grpc_face.id, grpc_face.surface_type, self, self._template._grpc_client) + for grpc_face in project_response.faces + ] + + return projected_faces def translate(self, direction: UnitVector3D, distance: Union[Quantity, Distance, Real]) -> None: return self._template.translate(direction, distance) From d1f5d021701eded01845fc8390f9c3f161c11797 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Mon, 17 Apr 2023 11:39:38 -0400 Subject: [PATCH 15/36] add test --- tests/integration/test_design.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 7b2f01cefd..20033edfb6 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -833,6 +833,7 @@ def test_project_and_imprint_curves(modeler: Modeler, skip_not_on_linux_service) """Test the projection of a set of curves on a body.""" # Create your design on the server side design = modeler.create_design("ExtrudeSlot") + comp = design.add_component("Comp1") # Create a Sketch object and draw a couple of slots imprint_sketch = Sketch() @@ -842,7 +843,7 @@ def test_project_and_imprint_curves(modeler: Modeler, skip_not_on_linux_service) # Extrude the sketch sketch = Sketch() sketch.box(Point2D([0, 0], UNITS.mm), Quantity(150, UNITS.mm), Quantity(150, UNITS.mm)) - body = design.extrude_sketch(name="MyBox", sketch=sketch, distance=Quantity(50, UNITS.mm)) + body = comp.extrude_sketch(name="MyBox", sketch=sketch, distance=Quantity(50, UNITS.mm)) body_faces = body.faces # Project the curves on the box @@ -885,6 +886,10 @@ def test_project_and_imprint_curves(modeler: Modeler, skip_not_on_linux_service) assert len(new_faces) == 2 assert len(body.faces) == 8 + # Make sure we have occurrence faces, not master + assert faces[0].id not in [face.id for face in body._template.faces] + assert new_faces[0].id not in [face.id for face in body._template.faces] + def test_copy_body(modeler: Modeler, skip_not_on_linux_service): """Test copying a body.""" From 7257809a635fd2032a501b882178c7f1c052eef1 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 18 Apr 2023 16:38:49 -0400 Subject: [PATCH 16/36] non-JSON working implementation --- src/ansys/geometry/core/designer/component.py | 34 +------ src/ansys/geometry/core/designer/design.py | 92 +++++++++++++------ 2 files changed, 67 insertions(+), 59 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index ab4840fd88..e1af573cd1 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -22,7 +22,7 @@ from ansys.api.geometry.v0.components_pb2_grpc import ComponentsStub from ansys.api.geometry.v0.models_pb2 import Direction, EntityIdentifier, Line from beartype import beartype as check_input_types -from beartype.typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union +from beartype.typing import TYPE_CHECKING, List, Optional, Tuple, Union from pint import Quantity from ansys.geometry.core.connection import ( @@ -144,7 +144,7 @@ def __init__( self._parent_component = parent_component self._is_alive = True self._shared_topology = None - self._transformed_part = None + self._transformed_part = transformed_part # Populate client data model if template: @@ -164,7 +164,7 @@ def __init__( # Recurse - Create more children components from template's remaining children self.__create_children(template) - else: + elif not read_existing_comp: # Create new Part and TransformedPart since this is creating a new "master" p = Part(uuid.uuid4(), f"p_{name}", [], []) tp = TransformedPart(uuid.uuid4(), f"tp_{name}", p) @@ -990,31 +990,3 @@ def __repr__(self) -> str: lines.append(f" N Components : {sum(alive_comps)}") lines.append(f" N Coordinate Systems : {len(self.coordinate_systems)}") return "\n".join(lines) - - def __read_existing_component(self, component_as_json: Dict) -> None: - # Given the existing component... - # - # Let's read the existing bodies first - bodies_json = component_as_json["bodies"] - subcomps_json = component_as_json["components"] - for body in bodies_json: - self._bodies.append( - Body( - body["masterid"], - body["name"], - self, - self._grpc_client, - is_surface=(not body["isclosed"]), - ) - ) - - # Now, once all bodies have been read, let's get all subcomponents - for subcomp_json in subcomps_json: - subcomp = Component("", self, self._grpc_client, read_existing_comp=True) - subcomp._id = subcomp_json["id"] - subcomp._name = subcomp_json["name"] - self._components.append(subcomp) - subcomp.__read_existing_component(subcomp_json) - - # Finally, return... just for readability purposes - return diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index f3d50a81d4..1f3cc78df9 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -9,12 +9,7 @@ CreateBeamCircularProfileRequest, ) from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub -from ansys.api.geometry.v0.designs_pb2 import ( - ExportRequest, - GetAsJsonRequest, - NewRequest, - SaveAsRequest, -) +from ansys.api.geometry.v0.designs_pb2 import ExportRequest, NewRequest, SaveAsRequest from ansys.api.geometry.v0.designs_pb2_grpc import DesignsStub from ansys.api.geometry.v0.materials_pb2 import AddToDocumentRequest from ansys.api.geometry.v0.materials_pb2_grpc import MaterialsStub @@ -24,16 +19,18 @@ from ansys.api.geometry.v0.models_pb2 import PartExportFormat from ansys.api.geometry.v0.namedselections_pb2_grpc import NamedSelectionsStub from beartype import beartype as check_input_types -from beartype.typing import Dict, List, Optional, Union +from beartype.typing import List, Optional, Union import numpy as np from pint import Quantity from ansys.geometry.core.connection import GrpcClient, plane_to_grpc_plane, point3d_to_grpc_point +from ansys.geometry.core.connection.conversions import grpc_matrix_to_matrix from ansys.geometry.core.designer.beam import BeamCircularProfile, BeamProfile -from ansys.geometry.core.designer.body import Body, MidSurfaceOffsetType +from ansys.geometry.core.designer.body import Body, MidSurfaceOffsetType, TemplateBody from ansys.geometry.core.designer.component import Component, SharedTopologyType from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.designer.face import Face +from ansys.geometry.core.designer.part import Part, TransformedPart from ansys.geometry.core.designer.selection import NamedSelection from ansys.geometry.core.errors import protect_grpc from ansys.geometry.core.materials import Material @@ -473,15 +470,6 @@ def __repr__(self): return "\n".join(lines) def __read_existing_design(self) -> None: - """Read existing design on the service with the connected client. - This method will just fill the ``Design`` object with all its - existing ``Component`` and ``Body`` objects. - """ - - import json - - # TODO: To be implemented... - # # We have to get back: # # - [X] Components @@ -492,9 +480,8 @@ def __read_existing_design(self) -> None: # - [ ] Beams # - [ ] CoordinateSystems # - [ ] SharedTopology - # - # - # First, let's start by getting the design object (i.e. root component) + + # Grab active design design = self._design_stub.GetActive(Empty()) if not design: raise RuntimeError("No existing design available at service level.") @@ -502,11 +489,60 @@ def __read_existing_design(self) -> None: self._id = design.main_part.id self._name = design.main_part.name - # Now that we have verified that there is an active design - # on the service, let's read it. - response = self._design_stub.GetAsJson(GetAsJsonRequest(id="")) - design_as_json: Dict = json.loads(response.json) - - # Having the information available as a JSON file, it is now time - # to start creating the different bodies, components... - self._Component__read_existing_component(design_as_json["design"]) + response = self._design_stub.GetAssembly(EntityIdentifier(id="")) + + # Store created objects + created_parts = [Part(p.id, p.name, [], []) for p in response.parts] + created_tps = [] + created_components = [self] + created_bodies = [] + + # Make dummy TP for design since server doesn't have one + self._transformed_part = TransformedPart("1", "tp_design", created_parts[0]) + + # Create TransformedParts + for tp in response.transformed_parts: + # Find Part to connect to + for part in created_parts: + if part.id == tp.part_master.id: + new_tp = TransformedPart( + tp.id, tp.name, part, grpc_matrix_to_matrix(tp.placement) + ) + created_tps.append(new_tp) + break + + # Create Components + for comp in response.components: + # Find parent Component to connect to + for parent in created_components: + if comp.parent_id == parent.id: + # Find TransformedPart to connect to + for tp in created_tps: + if comp.master_id == tp.id: + c = Component( + comp.name, + parent, + self._grpc_client, + preexisting_id=comp.id, + transformed_part=tp, + read_existing_comp=True, + ) + created_components.append(c) + parent.components.append(c) + break + break + + # Create Bodies + for body in response.bodies: + # Find Part to connect to + for part in created_parts: + if body.parent_id == part.id: + tb = TemplateBody(body.id, body.name, self._grpc_client) + part.bodies.append(tb) + created_bodies.append(tb) + break + + print(f"Parts created: {len(created_parts)}") + print(f"TransformedParts created: {len(created_tps)}") + print(f"Components created: {len(created_components)}") + print(f"Bodies created: {len(created_bodies)}") From c3e96e809caa06703f377c6282c7efa6c685b329 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 20 Apr 2023 12:21:14 -0400 Subject: [PATCH 17/36] read materials and named_selections --- src/ansys/geometry/core/designer/design.py | 103 ++++++++++-------- src/ansys/geometry/core/designer/selection.py | 15 ++- src/ansys/geometry/core/materials/property.py | 12 ++ 3 files changed, 82 insertions(+), 48 deletions(-) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 1f3cc78df9..056f5bcfc5 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -19,7 +19,7 @@ from ansys.api.geometry.v0.models_pb2 import PartExportFormat from ansys.api.geometry.v0.namedselections_pb2_grpc import NamedSelectionsStub from beartype import beartype as check_input_types -from beartype.typing import List, Optional, Union +from beartype.typing import Dict, List, Optional, Union import numpy as np from pint import Quantity @@ -33,7 +33,7 @@ from ansys.geometry.core.designer.part import Part, TransformedPart from ansys.geometry.core.designer.selection import NamedSelection from ansys.geometry.core.errors import protect_grpc -from ansys.geometry.core.materials import Material +from ansys.geometry.core.materials import Material, MaterialProperty, MaterialPropertyType from ansys.geometry.core.math import ( UNITVECTOR3D_X, UNITVECTOR3D_Y, @@ -474,13 +474,15 @@ def __read_existing_design(self) -> None: # # - [X] Components # - [X] Bodies - # - [ ] Materials - # - [ ] NamedSelections + # - [X] Materials + # - [X] NamedSelections # - [ ] BeamProfiles # - [ ] Beams # - [ ] CoordinateSystems # - [ ] SharedTopology + import time + start = time.time() # Grab active design design = self._design_stub.GetActive(Empty()) if not design: @@ -492,57 +494,72 @@ def __read_existing_design(self) -> None: response = self._design_stub.GetAssembly(EntityIdentifier(id="")) # Store created objects - created_parts = [Part(p.id, p.name, [], []) for p in response.parts] - created_tps = [] - created_components = [self] - created_bodies = [] + created_parts = {p.id: Part(p.id, p.name, [], []) for p in response.parts} + created_tps = {} + created_components = {self.id: self} + created_bodies = {} # Make dummy TP for design since server doesn't have one - self._transformed_part = TransformedPart("1", "tp_design", created_parts[0]) + self._transformed_part = TransformedPart("1", "tp_design", created_parts[self.id]) # Create TransformedParts for tp in response.transformed_parts: - # Find Part to connect to - for part in created_parts: - if part.id == tp.part_master.id: - new_tp = TransformedPart( - tp.id, tp.name, part, grpc_matrix_to_matrix(tp.placement) - ) - created_tps.append(new_tp) - break + part = created_parts.get(tp.part_master.id) + new_tp = TransformedPart(tp.id, tp.name, part, grpc_matrix_to_matrix(tp.placement)) + created_tps[tp.id] = new_tp # Create Components for comp in response.components: - # Find parent Component to connect to - for parent in created_components: - if comp.parent_id == parent.id: - # Find TransformedPart to connect to - for tp in created_tps: - if comp.master_id == tp.id: - c = Component( - comp.name, - parent, - self._grpc_client, - preexisting_id=comp.id, - transformed_part=tp, - read_existing_comp=True, - ) - created_components.append(c) - parent.components.append(c) - break - break + parent = created_components.get(comp.parent_id) + tp = created_tps.get(comp.master_id) + c = Component( + comp.name, + parent, + self._grpc_client, + preexisting_id=comp.id, + transformed_part=tp, + read_existing_comp=True, + ) + created_components[comp.id] = c + parent.components.append(c) # Create Bodies + # TODO: is_surface? for body in response.bodies: - # Find Part to connect to - for part in created_parts: - if body.parent_id == part.id: - tb = TemplateBody(body.id, body.name, self._grpc_client) - part.bodies.append(tb) - created_bodies.append(tb) - break + part = created_parts.get(body.parent_id) + tb = TemplateBody(body.id, body.name, self._grpc_client) + part.bodies.append(tb) + created_bodies[body.id] = tb + + # Create Materials + for material in response.materials: + properties = [] + density = Quantity(0) + for property in material.material_properties: + mp = MaterialProperty( + MaterialPropertyType.from_id(property.id), + property.display_name, + Quantity(property.value, property.units), + ) + properties.append(mp) + if mp.type == MaterialPropertyType.DENSITY: + density = mp.quantity + + m = Material(material.name, density, properties) + self.materials.append(m) + + # Create NamedSelections + for ns in response.named_selections: + new_ns = NamedSelection(ns.name, self._grpc_client, preexisting_id=ns.id) + self._named_selections[new_ns.name] = new_ns + + end = time.time() print(f"Parts created: {len(created_parts)}") - print(f"TransformedParts created: {len(created_tps)}") + print(f"TransformedParts created: {len(created_tps) + 1}") print(f"Components created: {len(created_components)}") print(f"Bodies created: {len(created_bodies)}") + print(f"Materials created: {len(self.materials)}") + print(f"NamedSelections created: {len(self.named_selections)}") + + print(f"\nRead Time: {end - start}") diff --git a/src/ansys/geometry/core/designer/selection.py b/src/ansys/geometry/core/designer/selection.py index e84d2f902f..61e05a6b86 100644 --- a/src/ansys/geometry/core/designer/selection.py +++ b/src/ansys/geometry/core/designer/selection.py @@ -44,6 +44,7 @@ def __init__( bodies: Optional[List[Body]] = None, faces: Optional[List[Face]] = None, edges: Optional[List[Edge]] = None, + preexisting_id: Optional[str] = None, ): """Constructor method for the ``NamedSelection`` class.""" @@ -65,11 +66,15 @@ def __init__( [ids.add(face.id) for face in faces] [ids.add(edge.id) for edge in edges] - named_selection_request = CreateRequest(name=name, members=ids) - self._grpc_client.log.debug("Requesting creation of named selection.") - new_named_selection = self._named_selections_stub.Create(named_selection_request) - self._id = new_named_selection.id - self._name = new_named_selection.name + if not preexisting_id: + named_selection_request = CreateRequest(name=name, members=ids) + self._grpc_client.log.debug("Requesting creation of named selection.") + new_named_selection = self._named_selections_stub.Create(named_selection_request) + self._id = new_named_selection.id + self._name = new_named_selection.name + else: + self._id = preexisting_id + self._name = name @property def id(self) -> str: diff --git a/src/ansys/geometry/core/materials/property.py b/src/ansys/geometry/core/materials/property.py index 934d67ca8b..70949ff92c 100644 --- a/src/ansys/geometry/core/materials/property.py +++ b/src/ansys/geometry/core/materials/property.py @@ -18,6 +18,18 @@ class MaterialPropertyType(Enum): TENSILE_STRENGTH = "TensileStrength" THERMAL_CONDUCTIVITY = "ThermalConductivity" + def from_id(id: str) -> "MaterialPropertyType": + relations = { + "General.Density.Mass": "Density", + "Linear.Isotropic.Emodulus": "ElasticModulus", + "Linear.Isotropic.Poisson": "PoissonsRatio", + "Linear.Isotropic.Gmodulus": "ShearModulus", + "Heat.Iso.Cp": "SpecificHeat", + "Nonlinear.vonMises.Ultimate": "TensileStrength", + "Heat.Iso.Conduction": "ThermalConductivity", + } + return MaterialPropertyType(relations.get(id)) + class MaterialProperty: """ From 2873987016afc983259341bc4bba2ae29d89de0b Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 20 Apr 2023 16:06:36 -0400 Subject: [PATCH 18/36] SharedTopology and CoordinateSystems --- .../geometry/core/connection/conversions.py | 27 +++++++ .../core/designer/coordinate_system.py | 79 +++++++++++-------- src/ansys/geometry/core/designer/design.py | 36 ++++++++- 3 files changed, 103 insertions(+), 39 deletions(-) diff --git a/src/ansys/geometry/core/connection/conversions.py b/src/ansys/geometry/core/connection/conversions.py index 1b76645204..4d7286903b 100644 --- a/src/ansys/geometry/core/connection/conversions.py +++ b/src/ansys/geometry/core/connection/conversions.py @@ -365,3 +365,30 @@ def grpc_matrix_to_matrix(m: GRPCMatrix) -> Matrix44: 8, ) ) + + +def grpc_frame_to_frame(frame: GRPCFrame) -> Frame: + return Frame( + Point3D( + [ + frame.origin.x, + frame.origin.y, + frame.origin.z, + ], + DEFAULT_UNITS.SERVER_LENGTH, + ), + UnitVector3D( + [ + frame.dir_x.x, + frame.dir_x.y, + frame.dir_x.z, + ] + ), + UnitVector3D( + [ + frame.dir_y.x, + frame.dir_y.y, + frame.dir_y.z, + ] + ), + ) diff --git a/src/ansys/geometry/core/designer/coordinate_system.py b/src/ansys/geometry/core/designer/coordinate_system.py index 344d7dabc4..c561cd79cb 100644 --- a/src/ansys/geometry/core/designer/coordinate_system.py +++ b/src/ansys/geometry/core/designer/coordinate_system.py @@ -2,7 +2,7 @@ from ansys.api.geometry.v0.coordinatesystems_pb2 import CreateRequest from ansys.api.geometry.v0.coordinatesystems_pb2_grpc import CoordinateSystemsStub -from beartype.typing import TYPE_CHECKING +from beartype.typing import TYPE_CHECKING, Optional from ansys.geometry.core.connection import GrpcClient, frame_to_grpc_frame from ansys.geometry.core.errors import protect_grpc @@ -33,7 +33,12 @@ class CoordinateSystem: @protect_grpc def __init__( - self, name: str, frame: Frame, parent_component: "Component", grpc_client: GrpcClient + self, + name: str, + frame: Frame, + parent_component: "Component", + grpc_client: GrpcClient, + preexisting_id: Optional[str] = None, ): """Constructor method for the ``CoordinateSystem`` class.""" @@ -41,41 +46,45 @@ def __init__( self._grpc_client = grpc_client self._coordinate_systems_stub = CoordinateSystemsStub(grpc_client.channel) - self._grpc_client.log.debug("Requesting creation of Coordinate System.") - new_coordinate_system = self._coordinate_systems_stub.Create( - CreateRequest( - parent=parent_component.id, - name=name, - frame=frame_to_grpc_frame(frame), + if not preexisting_id: + self._grpc_client.log.debug("Requesting creation of Coordinate System.") + new_coordinate_system = self._coordinate_systems_stub.Create( + CreateRequest( + parent=parent_component.id, + name=name, + frame=frame_to_grpc_frame(frame), + ) ) - ) - self._id = new_coordinate_system.id - self._name = new_coordinate_system.name - self._frame = Frame( - Point3D( - [ - new_coordinate_system.frame.origin.x, - new_coordinate_system.frame.origin.y, - new_coordinate_system.frame.origin.z, - ], - DEFAULT_UNITS.SERVER_LENGTH, - ), - UnitVector3D( - [ - new_coordinate_system.frame.dir_x.x, - new_coordinate_system.frame.dir_x.y, - new_coordinate_system.frame.dir_x.z, - ] - ), - UnitVector3D( - [ - new_coordinate_system.frame.dir_y.x, - new_coordinate_system.frame.dir_y.y, - new_coordinate_system.frame.dir_y.z, - ] - ), - ) + self._id = new_coordinate_system.id + self._name = new_coordinate_system.name + self._frame = Frame( + Point3D( + [ + new_coordinate_system.frame.origin.x, + new_coordinate_system.frame.origin.y, + new_coordinate_system.frame.origin.z, + ], + DEFAULT_UNITS.SERVER_LENGTH, + ), + UnitVector3D( + [ + new_coordinate_system.frame.dir_x.x, + new_coordinate_system.frame.dir_x.y, + new_coordinate_system.frame.dir_x.z, + ] + ), + UnitVector3D( + [ + new_coordinate_system.frame.dir_y.x, + new_coordinate_system.frame.dir_y.y, + new_coordinate_system.frame.dir_y.z, + ] + ), + ) + else: + self._name = name + self._frame = frame self._is_alive = True @property diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index a84fcafd04..978e3316b5 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -23,11 +23,17 @@ import numpy as np from pint import Quantity -from ansys.geometry.core.connection import GrpcClient, plane_to_grpc_plane, point3d_to_grpc_point -from ansys.geometry.core.connection.conversions import grpc_matrix_to_matrix +from ansys.geometry.core.connection import ( + GrpcClient, + grpc_frame_to_frame, + grpc_matrix_to_matrix, + plane_to_grpc_plane, + point3d_to_grpc_point, +) from ansys.geometry.core.designer.beam import Beam, BeamCircularProfile, BeamProfile from ansys.geometry.core.designer.body import Body, MidSurfaceOffsetType, TemplateBody from ansys.geometry.core.designer.component import Component, SharedTopologyType +from ansys.geometry.core.designer.coordinate_system import CoordinateSystem from ansys.geometry.core.designer.designpoint import DesignPoint from ansys.geometry.core.designer.edge import Edge from ansys.geometry.core.designer.face import Face @@ -517,8 +523,8 @@ def __read_existing_design(self) -> None: # - [X] NamedSelections # - [ ] BeamProfiles # - [ ] Beams - # - [ ] CoordinateSystems - # - [ ] SharedTopology + # - [X] CoordinateSystems + # - [X] SharedTopology import time start = time.time() @@ -592,13 +598,35 @@ def __read_existing_design(self) -> None: new_ns = NamedSelection(ns.name, self._grpc_client, preexisting_id=ns.id) self._named_selections[new_ns.name] = new_ns + # Create CoordinateSystems + num_created_coord_systems = 0 + for component_id, coordinate_systems in response.component_coord_systems.items(): + component = created_components.get(component_id) + for cs in coordinate_systems.coordinate_systems: + frame = grpc_frame_to_frame(cs.frame) + new_cs = CoordinateSystem(cs.name, frame, component, self._grpc_client, cs.id) + component.coordinate_systems.append(new_cs) + num_created_coord_systems += 1 + end = time.time() + # Set SharedTopology + # TODO: Maybe just add it to Component or Part message + # - we're starting to iterate through all the Components too much + # TODO: Make sure design doesn't need edge case attention + num_created_shared_topologies = 0 + for component_id, shared_topology_type in response.component_shared_topologies.items(): + component = created_components.get(component_id) + component._shared_topology = SharedTopologyType(shared_topology_type) + num_created_shared_topologies += 1 + print(f"Parts created: {len(created_parts)}") print(f"TransformedParts created: {len(created_tps) + 1}") print(f"Components created: {len(created_components)}") print(f"Bodies created: {len(created_bodies)}") print(f"Materials created: {len(self.materials)}") print(f"NamedSelections created: {len(self.named_selections)}") + print(f"CoordinateSystems created: {num_created_coord_systems}") + print(f"SharedTopologyTypes set: {num_created_shared_topologies}") print(f"\nRead Time: {end - start}") From d13916b72d7e8649783854b30c6016e15a8ca0ed Mon Sep 17 00:00:00 2001 From: jonahrb Date: Mon, 24 Apr 2023 10:22:11 -0400 Subject: [PATCH 19/36] cleanup, resolve comments --- .../geometry/core/connection/__init__.py | 1 + .../geometry/core/connection/conversions.py | 13 ++++ src/ansys/geometry/core/designer/component.py | 37 ++++----- .../core/designer/coordinate_system.py | 78 ++++++++++--------- src/ansys/geometry/core/designer/selection.py | 15 ++-- src/ansys/geometry/core/materials/property.py | 14 ++++ 6 files changed, 93 insertions(+), 65 deletions(-) diff --git a/src/ansys/geometry/core/connection/__init__.py b/src/ansys/geometry/core/connection/__init__.py index 3e159cae63..63c2d54713 100644 --- a/src/ansys/geometry/core/connection/__init__.py +++ b/src/ansys/geometry/core/connection/__init__.py @@ -3,6 +3,7 @@ from ansys.geometry.core.connection.client import GrpcClient from ansys.geometry.core.connection.conversions import ( frame_to_grpc_frame, + grpc_frame_to_frame, grpc_matrix_to_matrix, plane_to_grpc_plane, point3d_to_grpc_point, diff --git a/src/ansys/geometry/core/connection/conversions.py b/src/ansys/geometry/core/connection/conversions.py index 4d7286903b..21b6d5c244 100644 --- a/src/ansys/geometry/core/connection/conversions.py +++ b/src/ansys/geometry/core/connection/conversions.py @@ -368,6 +368,19 @@ def grpc_matrix_to_matrix(m: GRPCMatrix) -> Matrix44: def grpc_frame_to_frame(frame: GRPCFrame) -> Frame: + """Converts a ``ansys.api.geometry.Frame`` grpc message to a + :class:`ansys.geometry.core.math.Frame` class. + + Parameters + ---------- + GRPCFrame + Geometry service gRPC frame message. The unit for the frame origin is meters. + + Returns + ------- + frame : Frame + Resulting converted frame. + """ return Frame( Point3D( [ diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 2ded8c6042..b89649731f 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -149,29 +149,26 @@ def __init__( self._transformed_part = transformed_part # Populate client data model - if template: - if transformed_part: - # Re-use an existing tp if this is a nested instance - self._transformed_part = transformed_part - else: - # Create new TransformedPart, but use template's Part - tp = TransformedPart( - uuid.uuid4(), - f"tp_{name}", - template._transformed_part.part, - template._transformed_part.transform, - ) - tp.part.parts.append(tp) - self._transformed_part = tp + if template and not transformed_part: + # Create new TransformedPart, but use template's Part + tp = TransformedPart( + uuid.uuid4(), + f"tp_{name}", + template._transformed_part.part, + template._transformed_part.transform, + ) + tp.part.parts.append(tp) + self._transformed_part = tp # Recurse - Create more children components from template's remaining children self.__create_children(template) - elif not read_existing_comp: - # Create new Part and TransformedPart since this is creating a new "master" - p = Part(uuid.uuid4(), f"p_{name}", [], []) - tp = TransformedPart(uuid.uuid4(), f"tp_{name}", p) - p.parts.append(tp) - self._transformed_part = tp + return + + # This is an independent Component - Create new Part and TransformedPart + p = Part(uuid.uuid4(), f"p_{name}", [], []) + tp = TransformedPart(uuid.uuid4(), f"tp_{name}", p) + p.parts.append(tp) + self._transformed_part = tp @property def id(self) -> str: diff --git a/src/ansys/geometry/core/designer/coordinate_system.py b/src/ansys/geometry/core/designer/coordinate_system.py index c561cd79cb..bd645382f1 100644 --- a/src/ansys/geometry/core/designer/coordinate_system.py +++ b/src/ansys/geometry/core/designer/coordinate_system.py @@ -45,47 +45,49 @@ def __init__( self._parent_component = parent_component self._grpc_client = grpc_client self._coordinate_systems_stub = CoordinateSystemsStub(grpc_client.channel) + self._is_alive = True - if not preexisting_id: - self._grpc_client.log.debug("Requesting creation of Coordinate System.") - new_coordinate_system = self._coordinate_systems_stub.Create( - CreateRequest( - parent=parent_component.id, - name=name, - frame=frame_to_grpc_frame(frame), - ) - ) - - self._id = new_coordinate_system.id - self._name = new_coordinate_system.name - self._frame = Frame( - Point3D( - [ - new_coordinate_system.frame.origin.x, - new_coordinate_system.frame.origin.y, - new_coordinate_system.frame.origin.z, - ], - DEFAULT_UNITS.SERVER_LENGTH, - ), - UnitVector3D( - [ - new_coordinate_system.frame.dir_x.x, - new_coordinate_system.frame.dir_x.y, - new_coordinate_system.frame.dir_x.z, - ] - ), - UnitVector3D( - [ - new_coordinate_system.frame.dir_y.x, - new_coordinate_system.frame.dir_y.y, - new_coordinate_system.frame.dir_y.z, - ] - ), - ) - else: + if preexisting_id: self._name = name self._frame = frame - self._is_alive = True + self._id = preexisting_id + return + + self._grpc_client.log.debug("Requesting creation of Coordinate System.") + new_coordinate_system = self._coordinate_systems_stub.Create( + CreateRequest( + parent=parent_component.id, + name=name, + frame=frame_to_grpc_frame(frame), + ) + ) + + self._id = new_coordinate_system.id + self._name = new_coordinate_system.name + self._frame = Frame( + Point3D( + [ + new_coordinate_system.frame.origin.x, + new_coordinate_system.frame.origin.y, + new_coordinate_system.frame.origin.z, + ], + DEFAULT_UNITS.SERVER_LENGTH, + ), + UnitVector3D( + [ + new_coordinate_system.frame.dir_x.x, + new_coordinate_system.frame.dir_x.y, + new_coordinate_system.frame.dir_x.z, + ] + ), + UnitVector3D( + [ + new_coordinate_system.frame.dir_y.x, + new_coordinate_system.frame.dir_y.y, + new_coordinate_system.frame.dir_y.z, + ] + ), + ) @property def id(self) -> str: diff --git a/src/ansys/geometry/core/designer/selection.py b/src/ansys/geometry/core/designer/selection.py index 7e68f2a20d..d2c32036c6 100644 --- a/src/ansys/geometry/core/designer/selection.py +++ b/src/ansys/geometry/core/designer/selection.py @@ -80,15 +80,16 @@ def __init__( [ids.add(beam.id) for beam in beams] [ids.add(dp.id) for dp in design_points] - if not preexisting_id: - named_selection_request = CreateRequest(name=name, members=ids) - self._grpc_client.log.debug("Requesting creation of named selection.") - new_named_selection = self._named_selections_stub.Create(named_selection_request) - self._id = new_named_selection.id - self._name = new_named_selection.name - else: + if preexisting_id: self._id = preexisting_id self._name = name + return + + named_selection_request = CreateRequest(name=name, members=ids) + self._grpc_client.log.debug("Requesting creation of named selection.") + new_named_selection = self._named_selections_stub.Create(named_selection_request) + self._id = new_named_selection.id + self._name = new_named_selection.name @property def id(self) -> str: diff --git a/src/ansys/geometry/core/materials/property.py b/src/ansys/geometry/core/materials/property.py index 70949ff92c..ff4da456de 100644 --- a/src/ansys/geometry/core/materials/property.py +++ b/src/ansys/geometry/core/materials/property.py @@ -19,6 +19,20 @@ class MaterialPropertyType(Enum): THERMAL_CONDUCTIVITY = "ThermalConductivity" def from_id(id: str) -> "MaterialPropertyType": + """ + Return a ``ansys.geometry.core.materials.MaterialPropertyType`` from the Geometry Service + string representation of a property. + + Parameters + ---------- + id : str + Geometry Service string representation of a property type. + + Returns + ------- + MaterialPropertyType + Common name for property type. + """ relations = { "General.Density.Mass": "Density", "Linear.Isotropic.Emodulus": "ElasticModulus", From 4d0f509617f22ec4444827d88bd78f051ff78f83 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Fri, 28 Apr 2023 09:27:33 -0400 Subject: [PATCH 20/36] now using ObjectPaths for id --- src/ansys/geometry/core/designer/body.py | 3 +- src/ansys/geometry/core/designer/component.py | 71 +++++++++++-------- tests/integration/test_design_import.py | 9 ++- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index 270e6f3eb2..aee763359f 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -651,7 +651,8 @@ def copy(self, parent: "Component", name: str = None) -> "Body": response.master_id, copy_name, self._grpc_client, is_surface=self.is_surface ) parent._transformed_part.part.bodies.append(tb) - return Body(response.id, response.name, parent, tb) + body_id = parent.id + "/" + tb.id if parent.parent_component else tb.id + return Body(body_id, response.name, parent, tb) @protect_grpc def tessellate( diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index b89649731f..5839a1376f 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -132,7 +132,8 @@ def __init__( new_component = self._component_stub.Create( CreateRequest(name=name, parent=parent_component.id, template=template_id) ) - self._id = new_component.component.id + # Remove this method call once we know Service sends correct ObjectPath id + self._id = self.__remove_duplicate_ids(new_component.component.id) self._name = new_component.component.name else: self._name = name @@ -149,26 +150,28 @@ def __init__( self._transformed_part = transformed_part # Populate client data model - if template and not transformed_part: - # Create new TransformedPart, but use template's Part - tp = TransformedPart( - uuid.uuid4(), - f"tp_{name}", - template._transformed_part.part, - template._transformed_part.transform, - ) - tp.part.parts.append(tp) - self._transformed_part = tp + if template: + if not transformed_part: + # Create new TransformedPart, but use template's Part + tp = TransformedPart( + uuid.uuid4(), + f"tp_{name}", + template._transformed_part.part, + template._transformed_part.transform, + ) + tp.part.parts.append(tp) + self._transformed_part = tp # Recurse - Create more children components from template's remaining children self.__create_children(template) return - # This is an independent Component - Create new Part and TransformedPart - p = Part(uuid.uuid4(), f"p_{name}", [], []) - tp = TransformedPart(uuid.uuid4(), f"tp_{name}", p) - p.parts.append(tp) - self._transformed_part = tp + elif not read_existing_comp: + # This is an independent Component - Create new Part and TransformedPart + p = Part(uuid.uuid4(), f"p_{name}", [], []) + tp = TransformedPart(uuid.uuid4(), f"tp_{name}", p) + p.parts.append(tp) + self._transformed_part = tp @property def id(self) -> str: @@ -190,8 +193,7 @@ def bodies(self) -> List[Body]: """``Body`` objects inside of the component.""" bodies = [] for body in self._transformed_part.part.bodies: - id = self.id + body.id if self.parent_component else body.id - id = self.__fix_moniker(id) + id = f"{self.id}/{body.id}" if self.parent_component else body.id bodies.append(Body(id, body.name, self, body)) return bodies @@ -233,18 +235,14 @@ def shared_topology(self) -> Union[SharedTopologyType, None]: def __create_children(self, template: "Component") -> None: """Create new Component and Body children in ``self`` from ``template``.""" - for t_body in template.bodies: - new_id = self.id + ("~" + t_body.id.split("~")[-1]) - new_body = Body(new_id, t_body.name, self, t_body._template) - self.bodies.append(new_body) - for template_comp in template.components: + new_id = self.id + "/" + template_comp.id.split("/")[-1] new = Component( template_comp.name, self, self._grpc_client, template=template_comp, - preexisting_id=self.__fix_moniker(self.id + template_comp.id), + preexisting_id=new_id, transformed_part=template_comp._transformed_part, ) self.components.append(new) @@ -271,6 +269,15 @@ def __fix_moniker(self, string: str) -> str: x = "~" + x return x + def __remove_duplicate_ids(self, path: str) -> str: + # Convert to set but maintain order + res = [] + [res.append(x) for x in path.split("/") if x not in res] + id = "/".join(res) + if id != path: + print("Removed duplicate!") + return id + def get_world_transform(self) -> Matrix44: """ The full transformation matrix of this Component in world space. @@ -418,7 +425,9 @@ def extrude_sketch( response = self._bodies_stub.CreateExtrudedBody(request) tb = TemplateBody(response.master_id, name, self._grpc_client, is_surface=False) self._transformed_part.part.bodies.append(tb) - return Body(response.id, response.name, self, tb) + # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id + body_id = self.id + "/" + tb.id if self.parent_component else tb.id + return Body(body_id, response.name, self, tb) @protect_grpc @check_input_types @@ -464,7 +473,9 @@ def extrude_face(self, name: str, face: Face, distance: Union[Quantity, Distance tb = TemplateBody(response.master_id, name, self._grpc_client, is_surface=False) self._transformed_part.part.bodies.append(tb) - return Body(response.id, response.name, self, tb) + # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id + body_id = self.id + "/" + tb.id if self.parent_component else tb.id + return Body(body_id, response.name, self, tb) @protect_grpc @check_input_types @@ -500,7 +511,9 @@ def create_surface(self, name: str, sketch: Sketch) -> Body: tb = TemplateBody(response.master_id, name, self._grpc_client, is_surface=True) self._transformed_part.part.bodies.append(tb) - return Body(response.id, response.name, self, tb) + # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id + body_id = self.id + "/" + tb.id if self.parent_component else tb.id + return Body(body_id, response.name, self, tb) @protect_grpc @check_input_types @@ -539,7 +552,9 @@ def create_surface_from_face(self, name: str, face: Face) -> Body: tb = TemplateBody(response.master_id, name, self._grpc_client, is_surface=True) self._transformed_part.part.bodies.append(tb) - return Body(response.id, response.name, self, tb) + # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id + body_id = self.id + "/" + tb.id if self.parent_component else tb.id + return Body(body_id, response.name, self, tb) @check_input_types def create_coordinate_system(self, name: str, frame: Frame) -> CoordinateSystem: diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 6ce01cd92c..1ce2c98d18 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -3,7 +3,7 @@ from pint import Quantity from ansys.geometry.core import Modeler -from ansys.geometry.core.designer import Component +from ansys.geometry.core.designer import Component, Design from ansys.geometry.core.math import Point2D from ansys.geometry.core.misc import UNITS from ansys.geometry.core.sketch import Sketch @@ -15,6 +15,13 @@ def _checker_method(comp: Component, comp_ref: Component) -> None: assert comp.name == comp_ref.name assert len(comp.bodies) == len(comp_ref.bodies) assert len(comp.components) == len(comp_ref.components) + assert len(comp.coordinate_systems) == len(comp_ref.coordinate_systems) + assert comp.shared_topology == comp_ref.shared_topology + + # Check design features + if isinstance(comp, Design) and isinstance(comp_ref, Design): + assert len(comp.materials) == len(comp_ref.materials) + assert len(comp.named_selections) == len(comp_ref.named_selections) # Check bodies (if any) for body, body_ref in zip(comp.bodies, comp_ref.bodies): From 9dd47dacfd56581d03f6af2b82149d99d21b182b Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 2 May 2023 12:25:53 -0400 Subject: [PATCH 21/36] Bump ansys-api-geometry 0.2.8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8f38c7752a..6141805265 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-geometry==0.2.7", + "ansys-api-geometry==0.2.8", "beartype>=0.11.0", "google-api-python-client>=1.7.11", "googleapis-common-protos>=1.52.0", From 918e413e319c8756286bfd1f2025ad8d34d68e87 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 2 May 2023 12:30:16 -0400 Subject: [PATCH 22/36] cleanup --- src/ansys/geometry/core/designer/component.py | 11 +++++++++- .../core/designer/coordinate_system.py | 1 + src/ansys/geometry/core/designer/design.py | 20 +++++++++---------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 5839a1376f..49ad589c37 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -151,6 +151,7 @@ def __init__( # Populate client data model if template: + # If this is not a nested instance if not transformed_part: # Create new TransformedPart, but use template's Part tp = TransformedPart( @@ -270,7 +271,15 @@ def __fix_moniker(self, string: str) -> str: return x def __remove_duplicate_ids(self, path: str) -> str: - # Convert to set but maintain order + """ + Removes duplicate entries in the ID path. This is a safeguard, as the server + is known to have issues sometimes. + + Examples + -------- + This method converts "0:26/0:44/0:44/0:53" to "0:26/0:44/0:53". + """ + # Split the string into a list -> convert list into a set but maintain order res = [] [res.append(x) for x in path.split("/") if x not in res] id = "/".join(res) diff --git a/src/ansys/geometry/core/designer/coordinate_system.py b/src/ansys/geometry/core/designer/coordinate_system.py index bd645382f1..2087010708 100644 --- a/src/ansys/geometry/core/designer/coordinate_system.py +++ b/src/ansys/geometry/core/designer/coordinate_system.py @@ -47,6 +47,7 @@ def __init__( self._coordinate_systems_stub = CoordinateSystemsStub(grpc_client.channel) self._is_alive = True + # Create without going to server if preexisting_id: self._name = name self._frame = frame diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 978e3316b5..a4e0b1b30c 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -620,13 +620,13 @@ def __read_existing_design(self) -> None: component._shared_topology = SharedTopologyType(shared_topology_type) num_created_shared_topologies += 1 - print(f"Parts created: {len(created_parts)}") - print(f"TransformedParts created: {len(created_tps) + 1}") - print(f"Components created: {len(created_components)}") - print(f"Bodies created: {len(created_bodies)}") - print(f"Materials created: {len(self.materials)}") - print(f"NamedSelections created: {len(self.named_selections)}") - print(f"CoordinateSystems created: {num_created_coord_systems}") - print(f"SharedTopologyTypes set: {num_created_shared_topologies}") - - print(f"\nRead Time: {end - start}") + self._grpc_client.log.debug(f"Parts created: {len(created_parts)}") + self._grpc_client.log.debug(f"TransformedParts created: {len(created_tps) + 1}") + self._grpc_client.log.debug(f"Components created: {len(created_components)}") + self._grpc_client.log.debug(f"Bodies created: {len(created_bodies)}") + self._grpc_client.log.debug(f"Materials created: {len(self.materials)}") + self._grpc_client.log.debug(f"NamedSelections created: {len(self.named_selections)}") + self._grpc_client.log.debug(f"CoordinateSystems created: {num_created_coord_systems}") + self._grpc_client.log.debug(f"SharedTopologyTypes set: {num_created_shared_topologies}") + + self._grpc_client.log.debug(f"\nSuccessfully read design in: {end - start} s") From 698c02c5fbb73a05924c8ebfbd1851eabc19a4fd Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Wed, 3 May 2023 09:22:24 +0200 Subject: [PATCH 23/36] fix: pre-commit issues --- src/ansys/geometry/core/designer/component.py | 4 ++-- src/ansys/geometry/core/modeler.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 9c362b79fb..83e87fff28 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -274,8 +274,8 @@ def __fix_moniker(self, string: str) -> str: def __remove_duplicate_ids(self, path: str) -> str: """ - Removes duplicate entries in the ID path. This is a safeguard, as the server - is known to have issues sometimes. + Removes duplicate entries in the ID path. This is a safeguard, as the server is + known to have issues sometimes. Examples -------- diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index a7956723d9..5382f22c02 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -100,7 +100,8 @@ def create_design(self, name: str) -> "Design": return self._designs[-1] def read_existing_design(self) -> "Design": - """Read existing design on the service with the connected client. + """ + Read existing design on the service with the connected client. Returns ------- From 75e029b7798d954e86b528649d5b835b9955e8b8 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Wed, 10 May 2023 11:09:55 +0200 Subject: [PATCH 24/36] fix: solving code style issues --- src/ansys/geometry/core/connection/conversions.py | 3 +-- src/ansys/geometry/core/designer/component.py | 7 +++++-- src/ansys/geometry/core/materials/property.py | 3 +-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ansys/geometry/core/connection/conversions.py b/src/ansys/geometry/core/connection/conversions.py index 3fa1531cfc..07099edec6 100644 --- a/src/ansys/geometry/core/connection/conversions.py +++ b/src/ansys/geometry/core/connection/conversions.py @@ -373,8 +373,7 @@ def grpc_matrix_to_matrix(m: GRPCMatrix) -> Matrix44: def grpc_frame_to_frame(frame: GRPCFrame) -> Frame: - """Converts a ``ansys.api.geometry.Frame`` grpc message to a - :class:`ansys.geometry.core.math.Frame` class. + """Convert an ``ansys.api.geometry.Frame`` grpc message to a ``Frame`` class. Parameters ---------- diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 9cd93f7aa9..3bb0db85e3 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -270,8 +270,11 @@ def __fix_moniker(self, string: str) -> str: def __remove_duplicate_ids(self, path: str) -> str: """ - Removes duplicate entries in the ID path. This is a safeguard, as the server is - known to have issues sometimes. + Remove duplicate entries in the ID path. + + Notes + ----- + This is a safeguard, as the server is known to have issues sometimes. Examples -------- diff --git a/src/ansys/geometry/core/materials/property.py b/src/ansys/geometry/core/materials/property.py index 34c6b6a5ef..644272d53b 100644 --- a/src/ansys/geometry/core/materials/property.py +++ b/src/ansys/geometry/core/materials/property.py @@ -20,8 +20,7 @@ class MaterialPropertyType(Enum): def from_id(id: str) -> "MaterialPropertyType": """ - Return a ``ansys.geometry.core.materials.MaterialPropertyType`` from the Geometry Service - string representation of a property. + Return the ``MaterialPropertyType`` value from the service representation. Parameters ---------- From 215f98e0a96f7926759b06aa5d3b771f085a1fea Mon Sep 17 00:00:00 2001 From: jonahrb Date: Wed, 10 May 2023 10:16:24 -0400 Subject: [PATCH 25/36] add tests to linux --- src/ansys/geometry/core/designer/design.py | 13 ++++++++++++- tests/integration/test_design.py | 2 +- tests/integration/test_design_import.py | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 0c56197706..60c18d75fb 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -525,7 +525,7 @@ def __repr__(self) -> str: return "\n".join(lines) def __read_existing_design(self) -> None: - # We have to get back: + # Windows: # # - [X] Components # - [X] Bodies @@ -535,6 +535,17 @@ def __read_existing_design(self) -> None: # - [ ] Beams # - [X] CoordinateSystems # - [X] SharedTopology + + # Linux: + # + # - [X] Components + # - [X] Bodies + # - [X] Materials + # - [X] NamedSelections + # - [ ] BeamProfiles + # - [ ] Beams + # - [X] CoordinateSystems + # - [ ] SharedTopology import time start = time.time() diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 803ed22ae5..faedfec06d 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -1270,7 +1270,7 @@ def test_named_selections_design_points(modeler: Modeler): assert len(design.named_selections) == 0 -def test_component_instances(modeler: Modeler, skip_not_on_linux_service): +def test_component_instances(modeler: Modeler): """Test creation of ``Component`` instances and the effects this has.""" design_name = "ComponentInstance_Test" diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 1ce2c98d18..5713dcadba 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -33,7 +33,7 @@ def _checker_method(comp: Component, comp_ref: Component) -> None: _checker_method(subcomp, subcomp_ref) -def test_design_import_simple_case(modeler: Modeler, skip_not_on_linux_service): +def test_design_import_simple_case(modeler: Modeler): # With the given session let's create a the following Design # # Create your design on the server side From 17a1d6f160663bbc37238c912377607c465c7a2d Mon Sep 17 00:00:00 2001 From: jonahrb Date: Wed, 10 May 2023 10:25:57 -0400 Subject: [PATCH 26/36] cleanup --- src/ansys/geometry/core/designer/body.py | 1 + src/ansys/geometry/core/designer/component.py | 22 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/ansys/geometry/core/designer/body.py b/src/ansys/geometry/core/designer/body.py index 2958c1260d..8cce07f777 100644 --- a/src/ansys/geometry/core/designer/body.py +++ b/src/ansys/geometry/core/designer/body.py @@ -680,6 +680,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._transformed_part.part.bodies.append(tb) + # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id body_id = 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 3bb0db85e3..4b21dd6525 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -246,28 +246,6 @@ def __create_children(self, template: "Component") -> None: ) self.components.append(new) - def __fix_moniker(self, string: str) -> str: - """Format a chain of monikers so the service can identify the entities.""" - x = string.split("~")[1:] - if len(x) > 1: - x[0] = x[0].replace("sE", "~sO_~iI", 1) - for s in x[1:-1]: - index = x.index(s) - s = "~" + s if s[0] != "~" else s - s = s.replace("sE", "oO", 1) - s = s.replace("oE", "oO", 1) - if "iI" not in x[index + 1]: - s = s.replace("oO", "oO_~iI", 1) - s = s.replace("___", "__", 1) - x[index] = s - x[-1] = x[-1].replace("sE", "~oE", 1) - x = "".join(x) + "_" - else: - x = "".join(x) - if x[0] != "~": - x = "~" + x - return x - def __remove_duplicate_ids(self, path: str) -> str: """ Remove duplicate entries in the ID path. From 5ffeb845f448b65460d1b79194d2725d9ce2a4d5 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 11 May 2023 11:36:43 -0400 Subject: [PATCH 27/36] implement open file --- src/ansys/geometry/core/modeler.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index 0ef2c62d0e..04f7d6d4d7 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -120,7 +120,7 @@ def close(self) -> None: """``Modeler`` easy-access method to the client's close method.""" return self.client.close() - def upload_file(self, file_path: str) -> str: + def _upload_file(self, file_path: str, open_file: bool = False) -> str: """ Upload a file from the client to the server. ``file_path`` must include the extension. @@ -130,6 +130,8 @@ def upload_file(self, file_path: str) -> str: ---------- file_path : str The path of the file. Must include extension. + open_file : bool + Open the file in the Geometry Service. Returns ------- @@ -150,9 +152,31 @@ def upload_file(self, file_path: str) -> str: c_stub = CommandsStub(self._client.channel) - response = c_stub.UploadFile(UploadFileRequest(data=data, file_name=file_name)) + response = c_stub.UploadFile( + UploadFileRequest(data=data, file_name=file_name, open=open_file) + ) return response.file_path + def open_file(self, file_path: str) -> "Design": + """ + Open a file. ``file_path`` must include the extension. + + This imports a design into the service. On Windows, `.scdocx` and HOOPS Exchange formats + are supported. On Linux, only `.scdocx` is supported. + + Parameters + ---------- + file_path : str + The path of the file. Must include extension. + + Returns + ------- + Design + The newly imported design. + """ + self._upload_file(file_path, True) + return self.read_existing_design() + def __repr__(self) -> str: """Represent the modeler as a string.""" lines = [] From 81f94fe7d17af7ba868b14f789eb15ba5ab668b2 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 11 May 2023 11:44:45 -0400 Subject: [PATCH 28/36] cleanup --- src/ansys/geometry/core/designer/component.py | 8 ++++---- src/ansys/geometry/core/designer/selection.py | 5 ----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ansys/geometry/core/designer/component.py b/src/ansys/geometry/core/designer/component.py index 4b21dd6525..62c806dcb2 100644 --- a/src/ansys/geometry/core/designer/component.py +++ b/src/ansys/geometry/core/designer/component.py @@ -424,7 +424,7 @@ def extrude_sketch( tb = TemplateBody(response.master_id, name, self._grpc_client, is_surface=False) self._transformed_part.part.bodies.append(tb) # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id - body_id = self.id + "/" + tb.id if self.parent_component else tb.id + body_id = f"{self.id}/{tb.id}" if self.parent_component else tb.id return Body(body_id, response.name, self, tb) @protect_grpc @@ -473,7 +473,7 @@ def extrude_face(self, name: str, face: Face, distance: Union[Quantity, Distance tb = TemplateBody(response.master_id, name, self._grpc_client, is_surface=False) self._transformed_part.part.bodies.append(tb) # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id - body_id = self.id + "/" + tb.id if self.parent_component else tb.id + body_id = f"{self.id}/{tb.id}" if self.parent_component else tb.id return Body(body_id, response.name, self, tb) @protect_grpc @@ -512,7 +512,7 @@ def create_surface(self, name: str, sketch: Sketch) -> Body: tb = TemplateBody(response.master_id, name, self._grpc_client, is_surface=True) self._transformed_part.part.bodies.append(tb) # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id - body_id = self.id + "/" + tb.id if self.parent_component else tb.id + body_id = f"{self.id}/{tb.id}" if self.parent_component else tb.id return Body(body_id, response.name, self, tb) @protect_grpc @@ -554,7 +554,7 @@ def create_surface_from_face(self, name: str, face: Face) -> Body: tb = TemplateBody(response.master_id, name, self._grpc_client, is_surface=True) self._transformed_part.part.bodies.append(tb) # TODO: fix when DMS ObjectPath is fixed - previously we return the body with response.id - body_id = self.id + "/" + tb.id if self.parent_component else tb.id + body_id = f"{self.id}/{tb.id}" if self.parent_component else tb.id return Body(body_id, response.name, self, tb) @check_input_types diff --git a/src/ansys/geometry/core/designer/selection.py b/src/ansys/geometry/core/designer/selection.py index fc82cbde64..91886796eb 100644 --- a/src/ansys/geometry/core/designer/selection.py +++ b/src/ansys/geometry/core/designer/selection.py @@ -84,11 +84,6 @@ def __init__( [ids.add(beam.id) for beam in beams] [ids.add(dp.id) for dp in design_points] - if preexisting_id: - self._id = preexisting_id - self._name = name - return - named_selection_request = CreateRequest(name=name, members=ids) self._grpc_client.log.debug("Requesting creation of named selection.") new_named_selection = self._named_selections_stub.Create(named_selection_request) From b82fc7994048123e589f7fe9df3e3e50b6b04853 Mon Sep 17 00:00:00 2001 From: jonahrb Date: Thu, 11 May 2023 11:45:35 -0400 Subject: [PATCH 29/36] cleanup --- src/ansys/geometry/core/designer/design.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ansys/geometry/core/designer/design.py b/src/ansys/geometry/core/designer/design.py index 019e26d876..0a6279dd52 100644 --- a/src/ansys/geometry/core/designer/design.py +++ b/src/ansys/geometry/core/designer/design.py @@ -529,7 +529,6 @@ def __read_existing_design(self) -> None: # # TODO: Not all features implemented yet. Status is as follows # - # Windows: # # - [X] Components @@ -540,7 +539,7 @@ def __read_existing_design(self) -> None: # - [ ] Beams # - [X] CoordinateSystems # - [X] SharedTopology - + # # Linux: # # - [X] Components @@ -551,7 +550,7 @@ def __read_existing_design(self) -> None: # - [ ] Beams # - [X] CoordinateSystems # - [ ] SharedTopology - + # import time start = time.time() From 51c612842d9e7871f771eb87ccb0d029ded039fd Mon Sep 17 00:00:00 2001 From: jonahrb Date: Tue, 16 May 2023 11:24:49 -0400 Subject: [PATCH 30/36] fix test failure --- tests/integration/test_design.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 3049cd1cd3..bc8122a9c1 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -830,7 +830,7 @@ def test_upload_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory) assert file.exists() # Upload file - path_on_server = modeler.upload_file(file) + path_on_server = modeler._upload_file(file) assert path_on_server is not None From 904b48d778826ab907c3396b36fb424890d1754f Mon Sep 17 00:00:00 2001 From: Dastan Abdulla Date: Thu, 18 May 2023 15:27:48 -0400 Subject: [PATCH 31/36] created the test_open_file to download and open a file and make sure the design are the same --- tests/integration/test_design_import.py | 54 ++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 5713dcadba..68940f4411 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -1,10 +1,12 @@ """Test design import.""" +import numpy as np from pint import Quantity +import pytest from ansys.geometry.core import Modeler from ansys.geometry.core.designer import Component, Design -from ansys.geometry.core.math import Point2D +from ansys.geometry.core.math import Plane, Point2D, Point3D, UnitVector3D, Vector3D from ansys.geometry.core.misc import UNITS from ansys.geometry.core.sketch import Sketch @@ -89,3 +91,53 @@ def test_design_import_simple_case(modeler: Modeler): # Check the design _checker_method(read_design, design) + + +def test_open_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): + """Test creation of ``Component`` instances and the effects this has.""" + + design_name = "ComponentInstance_Test" + design = modeler.create_design(design_name) + + # Create a car + car1 = design.add_component("Car1") + comp1 = car1.add_component("A") + comp2 = car1.add_component("B") + wheel1 = comp2.add_component("Wheel1") + + # Create car base frame + sketch = Sketch().box(Point2D([5, 10]), 10, 20) + comp2.extrude_sketch("Base", sketch, 5) + + # Create first wheel + sketch = Sketch(Plane(direction_x=Vector3D([0, 1, 0]), direction_y=Vector3D([0, 0, 1]))) + sketch.circle(Point2D([0, 0]), 5) + wheel1.extrude_sketch("Wheel", sketch, -5) + + # Create 3 other wheels and move them into position + rotation_origin = Point3D([0, 0, 0]) + rotation_direction = UnitVector3D([0, 0, 1]) + + wheel2 = comp2.add_component("Wheel2", wheel1) + wheel2.modify_placement(Vector3D([0, 20, 0])) + + wheel3 = comp2.add_component("Wheel3", wheel1) + wheel3.modify_placement(Vector3D([10, 0, 0]), rotation_origin, rotation_direction, np.pi) + + wheel4 = comp2.add_component("Wheel4", wheel1) + wheel4.modify_placement(Vector3D([10, 20, 0]), rotation_origin, rotation_direction, np.pi) + + # Create 2nd car + car2 = design.add_component("Car2", car1) + car2.modify_placement(Vector3D([30, 0, 0])) + + # Create top of car - applies to BOTH cars + sketch = Sketch(Plane(Point3D([0, 5, 5]))).box(Point2D([5, 2.5]), 10, 5) + comp1.extrude_sketch("Top", sketch, 5) + + file = tmp_path_factory.mktemp("test_design") / "example.scdocx" + design.download(file) + design2 = modeler.open_file(file) + + # assert the two cars are the same + _checker_method(design, design2) From dc863184e0c1a8bec4d60fe944503bfe88aa45df Mon Sep 17 00:00:00 2001 From: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Date: Thu, 18 May 2023 15:36:18 -0400 Subject: [PATCH 32/36] Update tests/integration/test_design.py --- tests/integration/test_design.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index bc8122a9c1..ba408da38d 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -820,7 +820,8 @@ def test_download_file( def test_upload_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): - file = tmp_path_factory.mktemp("upload_file") / "example.scdocx" + """Test uploading a file to the server.""" + file = tmp_path_factory.mktemp("test_design") / "upload_example.scdocx" file_size = 1024 # Write random bytes From c37b2ad71f5a10c5ad2f2be8ac13543a80c5f0fa Mon Sep 17 00:00:00 2001 From: dastan-ansys <132925889+dastan-ansys@users.noreply.github.com> Date: Mon, 22 May 2023 10:15:19 -0400 Subject: [PATCH 33/36] Apply suggestions from code review Co-authored-by: Jonah Boling <56607167+jonahrb@users.noreply.github.com> --- tests/integration/test_design_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 68940f4411..3132df2ad6 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -135,7 +135,7 @@ def test_open_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): sketch = Sketch(Plane(Point3D([0, 5, 5]))).box(Point2D([5, 2.5]), 10, 5) comp1.extrude_sketch("Top", sketch, 5) - file = tmp_path_factory.mktemp("test_design") / "example.scdocx" + file = tmp_path_factory.mktemp("test_design_import") / "two_cars.scdocx" design.download(file) design2 = modeler.open_file(file) From 14262929d6a795306b7f5c06c1bcfec972538369 Mon Sep 17 00:00:00 2001 From: dastan-ansys <132925889+dastan-ansys@users.noreply.github.com> Date: Mon, 22 May 2023 10:17:37 -0400 Subject: [PATCH 34/36] Update test_design_import.py --- tests/integration/test_design_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 3132df2ad6..1ee17930eb 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -94,7 +94,7 @@ def test_design_import_simple_case(modeler: Modeler): def test_open_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): - """Test creation of ``Component`` instances and the effects this has.""" + """Test creation of a component, saving it to a file, and loading it again to a second component and make sure they have the same properties.""" design_name = "ComponentInstance_Test" design = modeler.create_design(design_name) From 7dace3872bdf24c76c8f15f941092e92306a8627 Mon Sep 17 00:00:00 2001 From: dastan-ansys <132925889+dastan-ansys@users.noreply.github.com> Date: Mon, 22 May 2023 10:18:51 -0400 Subject: [PATCH 35/36] Update test_design_import.py --- tests/integration/test_design_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 1ee17930eb..7f6fa2fc54 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -96,8 +96,8 @@ def test_design_import_simple_case(modeler: Modeler): def test_open_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): """Test creation of a component, saving it to a file, and loading it again to a second component and make sure they have the same properties.""" - design_name = "ComponentInstance_Test" - design = modeler.create_design(design_name) + car_design = "ComponentInstance_Test" + design = modeler.create_design(car_design) # Create a car car1 = design.add_component("Car1") From 67b45a47f45e26339086aa3840dfa5fd19ed2ec1 Mon Sep 17 00:00:00 2001 From: dastan-ansys <132925889+dastan-ansys@users.noreply.github.com> Date: Mon, 22 May 2023 10:29:18 -0400 Subject: [PATCH 36/36] Update test_design_import.py --- tests/integration/test_design_import.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 7f6fa2fc54..fdc1aa5ebd 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -94,10 +94,11 @@ def test_design_import_simple_case(modeler: Modeler): def test_open_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): - """Test creation of a component, saving it to a file, and loading it again to a second component and make sure they have the same properties.""" + """Test creation of a component, saving it to a file, and loading it again to a + second component and make sure they have the same properties.""" - car_design = "ComponentInstance_Test" - design = modeler.create_design(car_design) + design_name = "CarDesign_Test" + design = modeler.create_design(design_name) # Create a car car1 = design.add_component("Car1")