diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index 74caaf3012..04f7d6d4d7 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 @@ -118,6 +120,63 @@ 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, open_file: bool = False) -> str: + """ + 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 + ---------- + file_path : str + The path of the file. Must include extension. + open_file : bool + Open the file in the Geometry Service. + + Returns + ------- + file_path : str + The full path of the uploaded file on the server machine. + """ + import os + + if not os.path.exists(file_path): + 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.") + + 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=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 = [] diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index f805cc70e4..02f81da329 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -1,5 +1,7 @@ """Test design interaction.""" +import os + import numpy as np from pint import Quantity import pytest @@ -817,6 +819,22 @@ def test_download_file( assert fmd_file.exists() +def test_upload_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory): + """Test uploading a file to the server.""" + file = tmp_path_factory.mktemp("test_design") / "upload_example.scdocx" + file_size = 1024 + + # Write random bytes + with open(file, "wb") as fout: + fout.write(os.urandom(file_size)) + + assert file.exists() + + # Upload file + path_on_server = modeler._upload_file(file) + 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 diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 5713dcadba..fdc1aa5ebd 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,54 @@ 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 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 = "CarDesign_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_import") / "two_cars.scdocx" + design.download(file) + design2 = modeler.open_file(file) + + # assert the two cars are the same + _checker_method(design, design2)