From 3cd21779158a52cde1c14619f82f73062b67546f Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:09:49 +0200 Subject: [PATCH 01/27] feat: activate codecov on main branch (#131) --- .github/workflows/ci_cd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 85155548..da77e5c8 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -101,9 +101,9 @@ jobs: path: .cov retention-days: 7 - # - name: Upload coverage to Codecov - # if: env.MAIN_PYTHON_VERSION == matrix.python-version - # uses: codecov/codecov-action@v3 + - name: Upload coverage to Codecov + if: env.MAIN_PYTHON_VERSION == matrix.python-version + uses: codecov/codecov-action@v3 docs: name: Documentation From 1d94c3049266fbfae65001cbe70ae93756fa094d Mon Sep 17 00:00:00 2001 From: Jeff Moody <110494049+ansys-jmoody@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:26:47 -0500 Subject: [PATCH 02/27] Test coverage for Stackup (#130) --- src/ansys/sherlock/core/stackup.py | 105 +---- tests/test_stackup.py | 711 +++++++---------------------- 2 files changed, 197 insertions(+), 619 deletions(-) diff --git a/src/ansys/sherlock/core/stackup.py b/src/ansys/sherlock/core/stackup.py index 6ba8691f..8756d720 100644 --- a/src/ansys/sherlock/core/stackup.py +++ b/src/ansys/sherlock/core/stackup.py @@ -120,65 +120,8 @@ def _check_pcb_material_validity(self, manufacturer, grade, material): else: raise SherlockInvalidMaterialError("Laminate grade is invalid.") - def _check_thickness(self, thickness, thickness_unit, spec=None): - """Check thickness arguments to see if they are valid.""" - if thickness < 0: - if spec is not None: - raise SherlockInvalidThicknessArgumentError( - message=f"{str(spec).capitalize()} thickness is invalid." - ) - - raise SherlockInvalidThicknessArgumentError(message="Thickness is invalid.") - if thickness > 0: - if spec == "conductor" or spec == "power": - if thickness_unit == "oz": - return - if (self.LAMINATE_THICKNESS_UNIT_LIST is not None) and ( - thickness_unit not in self.LAMINATE_THICKNESS_UNIT_LIST - ): - if spec is not None: - raise SherlockInvalidThicknessArgumentError( - message=f"{str(spec).capitalize()} thickness units are invalid." - ) - - raise SherlockInvalidThicknessArgumentError(message="Thickness units are invalid.") - - def _check_layer_id(self, layerid, spec=None): - """Check layer argument to see if it is valid.""" - if layerid == "": - if spec is not None: - raise SherlockInvalidLayerIDError(message=f"Layer ID {spec} is missing.") - - raise SherlockInvalidLayerIDError(message="Layer ID is missing.") - - try: - id = int(layerid) - if id < 0: - raise SherlockInvalidLayerIDError( - message="Layer ID is invalid. It must be an integer greater than 0." - ) - except ValueError: - raise SherlockInvalidLayerIDError( - message="Layer ID is invalid. It must be an integer greater than 0." - ) - - def _check_conductor_percent(self, input): - """Check input string to see if it is a valid conductor percent.""" - if input == "": - return - - try: - percent = float(input) - if percent < 0 or percent > 100: - raise SherlockInvalidConductorPercentError( - message="Conductor percent is invalid. It must be between 0 and 100." - ) - except ValueError: - raise SherlockInvalidConductorPercentError( - message="Conductor percent is invalid. It must be between 0 and 100." - ) - - def _check_glass_construction_validity(self, input): + @staticmethod + def _check_glass_construction_validity(input): """Check input to see if it is a valid glass construction argument.""" if not isinstance(input, list): raise SherlockInvalidGlassConstructionError( @@ -191,7 +134,6 @@ def _check_glass_construction_validity(self, input): raise SherlockInvalidGlassConstructionError( message=f"Invalid layer {i}: Number of elements is wrong." ) - self._check_thickness(layer[2], layer[3]) except SherlockInvalidThicknessArgumentError as e: raise SherlockInvalidGlassConstructionError(message=f"Invalid layer {i}: {str(e)}") @@ -298,7 +240,6 @@ def gen_stackup( raise SherlockGenStackupError(message="Project name is invalid.") if cca_name == "": raise SherlockGenStackupError(message="CCA name is invalid.") - self._check_thickness(board_thickness, board_thickness_unit, spec="board") self._check_pcb_material_validity( pcb_material_manufacturer, pcb_material_grade, pcb_material ) @@ -308,13 +249,6 @@ def gen_stackup( ) if signal_layer_thickness < 0: raise SherlockGenStackupError(message="Conductor thickness is invalid.") - self._check_thickness( - signal_layer_thickness, signal_layer_thickness_unit, spec="conductor" - ) - self._check_thickness( - min_laminate_thickness, min_laminate_thickness_unit, spec="laminate" - ) - self._check_thickness(power_layer_thickness, power_layer_thickness_unit, spec="power") except SherlockGenStackupError as e: LOG.error(str(e)) raise e @@ -351,7 +285,7 @@ def gen_stackup( raise SherlockGenStackupError(response.message) LOG.info(response.message) - return + return response.value except SherlockGenStackupError as e: LOG.error(str(e)) raise e @@ -432,7 +366,6 @@ def update_conductor_layer( raise SherlockUpdateConductorLayerError(message="Project name is invalid.") if cca_name == "": raise SherlockUpdateConductorLayerError(message="CCA name is invalid.") - self._check_layer_id(layer, spec="conductor") if (type != "") and type not in self.LAYER_TYPE_LIST: raise SherlockUpdateConductorLayerError( message=( @@ -447,8 +380,6 @@ def update_conductor_layer( raise SherlockUpdateConductorLayerError( message="Conductor material is invalid." ) - self._check_thickness(thickness, thickness_unit, spec="conductor") - self._check_conductor_percent(conductor_percent) except SherlockUpdateConductorLayerError as e: LOG.error(str(e)) raise e @@ -483,7 +414,7 @@ def update_conductor_layer( raise SherlockUpdateConductorLayerError(response.message) LOG.info(response.message) - return + return response.value except SherlockUpdateConductorLayerError as e: LOG.error(str(e)) raise e @@ -603,10 +534,8 @@ def update_laminate_layer( raise SherlockUpdateLaminateLayerError(message="Project name is invalid.") if cca_name == "": raise SherlockUpdateLaminateLayerError(message="CCA name is invalid.") - self._check_layer_id(layer, spec="laminate") if manufacturer != "": self._check_pcb_material_validity(manufacturer, grade, material) - self._check_thickness(thickness, thickness_unit, spec="laminate") if construction_style != "": if (self.CONSTRUCTION_STYLE_LIST is not None) and ( construction_style not in self.CONSTRUCTION_STYLE_LIST @@ -623,7 +552,6 @@ def update_laminate_layer( conductor_material not in self.CONDUCTOR_MATERIAL_LIST ): raise SherlockUpdateLaminateLayerError(message="Conductor material is invalid.") - self._check_conductor_percent(conductor_percent) except SherlockUpdateLaminateLayerError as e: LOG.error(str(e)) raise e @@ -665,7 +593,7 @@ def update_laminate_layer( raise SherlockUpdateLaminateLayerError(response.message) LOG.info(response.message) - return + return response.value except SherlockUpdateLaminateLayerError as e: LOG.error(str(e)) raise e @@ -712,8 +640,10 @@ def list_conductor_layers(self, project): request = SherlockStackupService_pb2.ListConductorLayersRequest(project=project) response = self.stub.listConductorLayers(request) - layers = response.ccaConductorLayerProps - return layers + if response.returnCode.value == -1: + raise SherlockListConductorLayersError(response.returnCode.message) + + return response.ccaConductorLayerProps except SherlockListConductorLayersError as e: LOG.error(str(e)) @@ -766,8 +696,10 @@ def list_laminate_layers(self, project): request = SherlockStackupService_pb2.ListLaminatesRequest(project=project) response = self.stub.listLaminates(request) - layers = response.ccaLaminateProps - return layers + if response.returnCode.value == -1: + raise SherlockListLaminateLayersError(response.returnCode.message) + + return response.ccaLaminateProps except SherlockListLaminateLayersError as e: LOG.error(str(e)) @@ -814,8 +746,10 @@ def get_layer_count(self, project, cca_name): project=project, ccaName=cca_name ) response = self.stub.getLayerCount(request) - return response + if response.returnCode.value == -1: + raise SherlockGetLayerCountError(response.returnCode.message) + return response.count except SherlockGetLayerCountError as e: LOG.error(str(e)) raise e @@ -862,6 +796,9 @@ def get_stackup_props(self, project, cca_name): project=project, ccaName=cca_name ) response = self.stub.getStackupProps(request) + if response.returnCode.value == -1: + raise SherlockGetLayerCountError(response.returnCode.message) + return response except SherlockGetStackupPropsError as e: LOG.error(str(e)) @@ -912,8 +849,10 @@ def get_total_conductor_thickness(self, project, cca_name, thickness_unit): project=project, ccaName=cca_name, thicknessUnit=thickness_unit ) response = self.stub.getTotalConductorThickness(request) - return response + if response.returnCode.value == -1: + raise SherlockGetTotalConductorThicknessError(response.returnCode.message) + return response.totalThickness except SherlockGetTotalConductorThicknessError as e: LOG.error(str(e)) raise e diff --git a/tests/test_stackup.py b/tests/test_stackup.py index 637d13f3..bb8a8156 100644 --- a/tests/test_stackup.py +++ b/tests/test_stackup.py @@ -1,5 +1,7 @@ # © 2023 ANSYS, Inc. All rights reserved +import time + import grpc import pytest @@ -26,8 +28,8 @@ def test_all(): helper_test_update_laminate_layer(stackup) helper_test_list_conductor_layers(stackup) helper_test_list_laminate_layers(stackup) - helper_test_get_stackup_props(stackup) helper_test_get_layer_count(stackup) + helper_test_get_stackup_props(stackup) helper_test_get_total_conductor_thickness(stackup) @@ -77,28 +79,6 @@ def helper_test_gen_stackup(stackup): except SherlockGenStackupError as e: assert str(e) == "Generate stackup error: CCA name is invalid." - try: - stackup.gen_stackup( - "Test", - "Card", - -5, - "mil", - "Generic", - "FR-4", - "Generic FR-4", - 6, - 0.5, - "oz", - 1.0, - "mil", - False, - 1.0, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Board thickness is invalid." - try: stackup.gen_stackup( "Test", @@ -145,147 +125,15 @@ def helper_test_gen_stackup(stackup): except SherlockGenStackupError as e: assert str(e) == "Generate stackup error: Conductor thickness is invalid." - try: - stackup.gen_stackup( - "Test", - "Card", - 82.6, - "mil", - "Generic", - "FR-4", - "Generic FR-4", - 6, - 0.5, - "oz", - -10, - "mil", - False, - 1.0, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Laminate thickness is invalid." - - try: - stackup.gen_stackup( - "Test", - "Card", - 82.6, - "mil", - "Generic", - "FR-4", - "Generic FR-4", - 6, - 0.5, - "oz", - 1.0, - "mil", - False, - -1, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Power thickness is invalid." - if stackup._is_connection_up(): try: stackup.gen_stackup( - "Test", - "Card", - 82.6, - "Invalid", - "Generic", - "FR-4", - "Generic FR-4", - 6, - 0.5, - "oz", - 1.0, - "mil", - False, - 1.0, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Board thickness units are invalid." - - try: - stackup.gen_stackup( - "Test", - "Card", - 82.6, - "mil", - "Invalid", - "FR-4", - "Generic FR-4", - 6, - 0.5, - "oz", - 1.0, - "mil", - False, - 1.0, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Laminate manufacturer is invalid." - - try: - stackup.gen_stackup( - "Test", - "Card", - 82.6, - "mil", - "Generic", - "Invalid", - "Generic FR-4", - 6, - 0.5, - "oz", - 1.0, - "mil", - False, - 1.0, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Laminate grade is invalid." - - try: - stackup.gen_stackup( - "Test", + "Invalid Project", "Card", 82.6, "mil", "Generic", "FR-4", - "Invalid", - 6, - 0.5, - "oz", - 1.0, - "mil", - False, - 1.0, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Laminate material is invalid." - - try: - stackup.gen_stackup( - "Test", - "Card", - 82.6, - "mil", - "Hitachi", - "FR-4", "Generic FR-4", 6, 0.5, @@ -297,57 +145,13 @@ def helper_test_gen_stackup(stackup): "mil", ) pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Laminate material is invalid." - - try: - stackup.gen_stackup( - "Test", - "Card", - 82.6, - "mil", - "Generic", - "FR-4", - "Generic FR-4", - 6, - 0.5, - "Invalid", - 1.0, - "mil", - False, - 1.0, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Conductor thickness units are invalid." - - try: - stackup.gen_stackup( - "Test", - "Card", - 82.6, - "mil", - "Generic", - "FR-4", - "Generic FR-4", - 6, - 0.5, - "oz", - 1.0, - "Invalid", - False, - 1.0, - "mil", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Laminate thickness units are invalid." + except Exception as e: + assert type(e) == SherlockGenStackupError try: - stackup.gen_stackup( - "Test", - "Card", + result = stackup.gen_stackup( + "Tutorial Project", + "Main Board", 82.6, "mil", "Generic", @@ -360,11 +164,13 @@ def helper_test_gen_stackup(stackup): "mil", False, 1.0, - "Invalid", + "mil", ) - pytest.fail("No exception raised when using an invalid parameter") + assert result == 0 + # wait for the process to finish to allow tests that modify a layer to succeed + time.sleep(2) except SherlockGenStackupError as e: - assert str(e) == "Generate stackup error: Power thickness units are invalid." + pytest.fail(str(e)) def helper_test_update_conductor_layer(stackup): @@ -401,60 +207,6 @@ def helper_test_update_conductor_layer(stackup): except SherlockUpdateConductorLayerError as e: assert str(e) == "Update conductor layer error: CCA name is invalid." - try: - stackup.update_conductor_layer( - "Test", - "Card", - "", - "POWER", - "COPPER", - 1.0, - "oz", - "94.2", - "Generic FR-4 Generic FR-4", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateConductorLayerError as e: - assert str(e) == "Update conductor layer error: Layer ID conductor is missing." - - try: - stackup.update_conductor_layer( - "Test", - "Card", - "-4", - "POWER", - "COPPER", - 1.0, - "oz", - "94.2", - "Generic FR-4 Generic FR-4", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateConductorLayerError as e: - assert ( - str(e) == "Update conductor layer error: " - "Layer ID is invalid. It must be an integer greater than 0." - ) - - try: - stackup.update_conductor_layer( - "Test", - "Card", - "Invalid", - "POWER", - "COPPER", - 1.0, - "oz", - "94.2", - "Generic FR-4 Generic FR-4", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateConductorLayerError as e: - assert ( - str(e) == "Update conductor layer error: Layer ID is invalid. " - "It must be an integer greater than 0." - ) - try: stackup.update_conductor_layer( "Test", @@ -491,76 +243,41 @@ def helper_test_update_conductor_layer(stackup): except SherlockUpdateConductorLayerError as e: assert str(e) == "Update conductor layer error: Conductor material is invalid." - try: - stackup.update_conductor_layer( - "Test", - "Card", - "3", - "POWER", - "COPPER", - -4, - "oz", - "94.2", - "Generic FR-4 Generic FR-4", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateConductorLayerError as e: - assert str(e) == "Update conductor layer error: Conductor thickness is invalid." - if stackup._is_connection_up(): try: stackup.update_conductor_layer( - "Test", - "Card", + "Invalid Project", + "Main Board", "3", "POWER", "COPPER", - 1.0, - "Invalid", - "94.2", - "Generic FR-4 Generic FR-4", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateConductorLayerError as e: - assert str(e) == "Update conductor layer error: Conductor thickness units are invalid." - - try: - stackup.update_conductor_layer( - "Test", - "Card", - "3", - "POWER", - "COPPER", - 0, - "oz", - "105", - "Generic FR-4 Generic FR-4", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateConductorLayerError as e: - assert ( - str(e) == "Update conductor layer error: " - "Conductor percent is invalid. It must be between 0 and 100." - ) - - try: - stackup.update_conductor_layer( - "Test", - "Card", - "3", - "POWER", - "COPPER", - 1.0, - "oz", - "Invalid", - "Generic FR-4 Generic FR-4", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateConductorLayerError as e: - assert ( - str(e) == "Update conductor layer error: " - "Conductor percent is invalid. It must be between 0 and 100." - ) + 2.0, + "oz", + "94.2", + "Generic FR-4 Generic FR-4", + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockUpdateConductorLayerError + + try: + # NOTE: Don't modify the thickness because that changes + # the result of get_total_conductor_thickness. + result = stackup.update_conductor_layer( + "Tutorial Project", + "Main Board", + "3", + "POWER", + "ALUMINA", + # thickness=0.5, + # thickness_unit="oz", + conductor_percent="94.2", + resin_material="Generic FR-4 Generic FR-4", + ) + assert result == 0 + time.sleep(1) + except SherlockUpdateConductorLayerError as e: + pytest.fail(str(e)) def helper_test_update_laminate_layer(stackup): @@ -606,72 +323,6 @@ def helper_test_update_laminate_layer(stackup): except SherlockUpdateLaminateLayerError as e: assert str(e) == "Update laminate layer error: CCA name is invalid." - try: - stackup.update_laminate_layer( - "Test", - "Card", - "", - "Generic", - "FR-4", - "Generic FR-4", - 0.015, - "in", - "106", - [("106", 68.0, 0.015, "in")], - "E-GLASS", - "COPPER", - "0.0", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert str(e) == "Update laminate layer error: Layer ID laminate is missing." - - try: - stackup.update_laminate_layer( - "Test", - "Card", - "-2", - "Generic", - "FR-4", - "Generic FR-4", - 0.015, - "in", - "106", - [("106", 68.0, 0.015, "in")], - "E-GLASS", - "COPPER", - "0.0", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert ( - str(e) == "Update laminate layer error: Layer ID is invalid. " - "It must be an integer greater than 0." - ) - - try: - stackup.update_laminate_layer( - "Test", - "Card", - "Invalid", - "Generic", - "FR-4", - "Generic FR-4", - 0.015, - "in", - "106", - [("106", 68.0, 0.015, "in")], - "E-GLASS", - "COPPER", - "0.0", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert ( - str(e) == "Update laminate layer error: Layer ID is invalid. " - "It must be an integer greater than 0." - ) - if stackup._is_connection_up(): try: stackup.update_laminate_layer( @@ -693,7 +344,6 @@ def helper_test_update_laminate_layer(stackup): except SherlockUpdateLaminateLayerError as e: assert str(e) == "Update laminate layer error: Laminate manufacturer is invalid." - if stackup._is_connection_up(): try: stackup.update_laminate_layer( "Test", @@ -714,7 +364,6 @@ def helper_test_update_laminate_layer(stackup): except SherlockUpdateLaminateLayerError as e: assert str(e) == "Update laminate layer error: Laminate grade is invalid." - if stackup._is_connection_up(): try: stackup.update_laminate_layer( "Test", @@ -737,49 +386,8 @@ def helper_test_update_laminate_layer(stackup): try: stackup.update_laminate_layer( - "Test", - "Card", - "2", - "Generic", - "FR-4", - "Generic FR-4", - -0.015, - "in", - "106", - [("106", 68.0, 0.015, "in")], - "E-GLASS", - "COPPER", - "0.0", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert str(e) == "Update laminate layer error: Laminate thickness is invalid." - - if stackup._is_connection_up(): - try: - stackup.update_laminate_layer( - "Test", - "Card", - "2", - "Generic", - "FR-4", - "Generic FR-4", - 0.015, - "Invalid", - "106", - [("106", 68.0, 0.015, "in")], - "E-GLASS", - "COPPER", - "0.0", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert str(e) == "Update laminate layer error: Laminate thickness units are invalid." - - try: - stackup.update_laminate_layer( - "Test", - "Card", + "Tutorial Project", + "Main Board", "2", "Generic", "FR-4", @@ -819,54 +427,10 @@ def helper_test_update_laminate_layer(stackup): "Invalid layer 0: Number of elements is wrong." ) - try: - stackup.update_laminate_layer( - "Test", - "Card", - "2", - "Generic", - "FR-4", - "Generic FR-4", - 0.015, - "in", - "106", - [("106", 68.0, -0.015, "in")], - "E-GLASS", - "COPPER", - "0.0", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert str(e) == "Update laminate layer error: Invalid layer 0: Thickness is invalid." - - if stackup._is_connection_up(): - try: - stackup.update_laminate_layer( - "Test", - "Card", - "2", - "Generic", - "FR-4", - "Generic FR-4", - 0.015, - "in", - "106", - [("106", 68.0, 0.015, "Invalid")], - "E-GLASS", - "COPPER", - "0.0", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert ( - str(e) == "Update laminate layer error: " - "Invalid layer 0: Thickness units are invalid." - ) - if stackup._is_connection_up(): try: stackup.update_laminate_layer( - "Test", + "Invalid Project", "Card", "2", "Generic", @@ -876,57 +440,34 @@ def helper_test_update_laminate_layer(stackup): "in", "106", [("106", 68.0, 0.015, "in")], - "Invalid", + "E-GLASS", "COPPER", "0.0", ) pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert str(e) == "Update laminate layer error: Fiber material is invalid." + except Exception as e: + assert type(e) == SherlockUpdateLaminateLayerError - if stackup._is_connection_up(): try: - stackup.update_laminate_layer( - "Test", - "Card", + result = stackup.update_laminate_layer( + "Tutorial Project", + "Main Board", "2", "Generic", "FR-4", "Generic FR-4", - 0.015, - "in", + 15.4, + "mil", "106", - [("106", 68.0, 0.015, "in")], + [("106", 68.0, 0.0154, "in")], "E-GLASS", - "Invalid", + "COPPER", "0.0", ) - pytest.fail("No exception raised when using an invalid parameter") + assert result == 0 + time.sleep(1) except SherlockUpdateLaminateLayerError as e: - assert str(e) == "Update laminate layer error: Conductor material is invalid." - - try: - stackup.update_laminate_layer( - "Test", - "Card", - "2", - "", - "FR-4", - "Generic FR-4", - 0, - "Invalid", - "106", - [("106", 68.0, 0.015, "in")], - "", - "", - "101", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockUpdateLaminateLayerError as e: - assert str(e) == ( - "Update laminate layer error: " - "Conductor percent is invalid. It must be between 0 and 100." - ) + pytest.fail(str(e)) def helper_test_list_conductor_layers(stackup): @@ -937,6 +478,26 @@ def helper_test_list_conductor_layers(stackup): except SherlockListConductorLayersError as e: assert str(e) == "List conductor layer error: Project name is invalid." + if stackup._is_connection_up(): + try: + stackup.list_conductor_layers("Invalid Project") + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockListConductorLayersError + + try: + layer_properties_per_board = stackup.list_conductor_layers("Tutorial Project") + assert len(layer_properties_per_board) == 1 + layer_properties_of_board = layer_properties_per_board[0] + assert layer_properties_of_board.ccaName == "Main Board" + layer_properties_per_layer = layer_properties_of_board.conductorLayerProps + assert len(layer_properties_per_layer) == 6, "Incorrect number of conductor layers" + layer_properties = layer_properties_per_layer[0] + assert layer_properties.layer == "1" + assert layer_properties.type == "SIGNAL" + except SherlockListConductorLayersError as e: + pytest.fail(str(e)) + def helper_test_list_laminate_layers(stackup): """Test list_laminate_layers API""" @@ -946,25 +507,24 @@ def helper_test_list_laminate_layers(stackup): except SherlockListLaminateLayersError as e: assert str(e) == "List laminate layer error: Project name is invalid." + if stackup._is_connection_up(): + try: + stackup.list_laminate_layers("Invalid Project") + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockListLaminateLayersError -def helper_test_get_stackup_props(stackup): - """Test get_stackup_props API""" - try: - stackup.get_stackup_props( - "", - "Card", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGetStackupPropsError as e: - assert str(e) == "Get stackup prop error: Project name is invalid." - try: - stackup.get_stackup_props( - "Test", - "", - ) - pytest.fail("No exception raised when using an invalid parameter") - except SherlockGetStackupPropsError as e: - assert str(e) == "Get stackup prop error: CCA name is invalid." + try: + layer_properties_per_board = stackup.list_laminate_layers("Tutorial Project") + assert len(layer_properties_per_board) == 1 + layer_properties_of_board = layer_properties_per_board[0] + assert layer_properties_of_board.ccaName == "Main Board" + layer_properties_per_layer = layer_properties_of_board.laminateProps + assert len(layer_properties_per_layer) == 5, "Incorrect number of laminate layers" + layer_properties = layer_properties_per_layer[0] + assert layer_properties.layer == "2" + except SherlockListLaminateLayersError as e: + pytest.fail(str(e)) def helper_test_get_layer_count(stackup): @@ -1000,6 +560,64 @@ def helper_test_get_layer_count(stackup): except SherlockGetLayerCountError as e: assert str(e) == "Get layer count error: CCA name is invalid." + if stackup._is_connection_up(): + try: + stackup.get_layer_count( + "Tutorial Project", + "Invalid CCA", + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockGetLayerCountError + + try: + layer_count = stackup.get_layer_count( + "Tutorial Project", + "Main Board", + ) + assert layer_count == 11 + except SherlockGetLayerCountError as e: + pytest.fail(str(e)) + + +def helper_test_get_stackup_props(stackup): + """Test get_stackup_props API""" + try: + stackup.get_stackup_props( + "", + "Card", + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockGetStackupPropsError as e: + assert str(e) == "Get stackup prop error: Project name is invalid." + try: + stackup.get_stackup_props( + "Test", + "", + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockGetStackupPropsError as e: + assert str(e) == "Get stackup prop error: CCA name is invalid." + + if stackup._is_connection_up(): + try: + stackup.get_stackup_props( + "Tutorial Project", + "Invalid CCA", + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockGetLayerCountError + + try: + stackup_properties = stackup.get_stackup_props( + "Tutorial Project", + "Main Board", + ) + assert stackup_properties.conductorLayersCnt == "6" + except SherlockGetLayerCountError as e: + pytest.fail(str(e)) + def helper_test_get_total_conductor_thickness(stackup): try: @@ -1020,6 +638,27 @@ def helper_test_get_total_conductor_thickness(stackup): except SherlockGetTotalConductorThicknessError as e: assert str(e) == "Get total conductor thickness error: Invalid thickness unit" + if stackup._is_connection_up(): + try: + stackup.get_total_conductor_thickness( + "Tutorial Project", + "Invalid CCA", + "mm", + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockGetTotalConductorThicknessError + + try: + thickness = stackup.get_total_conductor_thickness( + "Tutorial Project", + "Main Board", + "oz", + ) + assert thickness == 3 + except SherlockGetTotalConductorThicknessError as e: + pytest.fail(str(e)) + if __name__ == "__main__": test_all() From ebbbb0c24a3b87bf37ba6c7e0c05fa9b57766281 Mon Sep 17 00:00:00 2001 From: ansys-eermovic <136727891+ansys-eermovic@users.noreply.github.com> Date: Wed, 21 Jun 2023 16:09:22 -0400 Subject: [PATCH 03/27] Shockapi (#129) --- AUTHORS.md | 1 + doc/source/api/analysis.rst | 2 + pyproject.toml | 4 +- src/ansys/sherlock/core/analysis.py | 251 +++++++++++++++++++++++++++- src/ansys/sherlock/core/errors.py | 12 ++ tests/test_analysis.py | 188 +++++++++++++++++++++ 6 files changed, 454 insertions(+), 4 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 2096534d..7ff64eb3 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -7,6 +7,7 @@ ## Contributors * [Alexander Kaszynski](https://github.com/akaszynski) +* [Ethan Ermovick](https://github.com/ansys-eermovic) * [Jeff Moody](https://github.com/ansys-jmoody) * [Paul Walters](https://github.com/ansys-pwalters) * [Roberto Pastor](https://github.com/RobPasMue) diff --git a/doc/source/api/analysis.rst b/doc/source/api/analysis.rst index deda9cbc..91eadefd 100644 --- a/doc/source/api/analysis.rst +++ b/doc/source/api/analysis.rst @@ -11,10 +11,12 @@ The ``analysis`` module contains all analysis capabilities. :toctree: _autosummary ansys.sherlock.core.analysis.Analysis.get_harmonic_vibe_input_fields + ansys.sherlock.core.analysis.Analysis.get_mechanical_shock_input_fields ansys.sherlock.core.analysis.Analysis.get_random_vibe_input_fields ansys.sherlock.core.analysis.Analysis.run_analysis ansys.sherlock.core.analysis.Analysis.run_strain_map_analysis ansys.sherlock.core.analysis.Analysis.update_harmonic_vibe_props + ansys.sherlock.core.analysis.Analysis.update_mechanical_shock_props ansys.sherlock.core.analysis.Analysis.update_natural_frequency_props ansys.sherlock.core.analysis.Analysis.update_pcb_modeling_props ansys.sherlock.core.analysis.Analysis.update_random_vibe_props \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7bf75b8b..852a3be4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "ansys-sherlock-core" -version = "0.2.dev0" +version = "0.3.dev0" description = "A python wrapper for Ansys Sherlock" readme = "README.rst" requires-python = ">=3.8,<4" @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-sherlock==0.1.14", + "ansys-api-sherlock==0.1.15", "grpcio>=1.17", "importlib-metadata>=4.0,<5; python_version<='3.8'", "protobuf~=3.20", diff --git a/src/ansys/sherlock/core/analysis.py b/src/ansys/sherlock/core/analysis.py index 041bfcd6..d527f07a 100644 --- a/src/ansys/sherlock/core/analysis.py +++ b/src/ansys/sherlock/core/analysis.py @@ -14,6 +14,7 @@ SherlockRunAnalysisError, SherlockRunStrainMapAnalysisError, SherlockUpdateHarmonicVibePropsError, + SherlockUpdateMechanicalShockPropsError, SherlockUpdateNaturalFrequencyPropsError, SherlockUpdatePcbModelingPropsError, SherlockUpdateRandomVibePropsError, @@ -33,6 +34,8 @@ def __init__(self, channel): "analysisTemp (optional)": "analysis_temp", "analysisTempUnits": "analysis_temp_units", "analysisTempUnits (optional)": "analysis_temp_units", + "criticalStrainShock": "critical_strain_shock", + "criticalStrainShockUnits": "critical_strain_shock_units", "filterByEventFrequency": "filter_by_event_frequency", "forceModelRebuild": "force_model_rebuild", "harmonicVibeDamping": "harmonic_vibe_damping", @@ -48,6 +51,7 @@ def __init__(self, channel): "randomVibeDamping": "random_vibe_damping", "requireMaterialAssignmentEnabled": "require_material_assignment_enabled", "reuseModalAnalysis": "reuse_modal_analysis", + "shockResultCount": "shock_result_count", "strainMapNaturalFreqs": "strain_map_natural_freqs", } @@ -233,14 +237,14 @@ def update_harmonic_vibe_props( - filter_by_event_frequency: bool Indicates if harmonic results outside analysis event range are included. This parameter is not used for NX Nastran analysis. - - natural_freq_min: double + - natural_freq_min: int Minimum frequency. The default is ``None``. This parameter is for NX Nastran analysis only. - natural_freq_min_units: str Minimum frequency units. The default is ``None``. Options are ``"HZ"``, ``"KHZ"``, ``"MHZ"``, and ``"GHZ"``. This parameter is for NX Nastran analysis only. - - natural_freq_max: double + - natural_freq_max: int Maximum frequency. The default is ``None``. This parameter is for NX Nastran analysis only. - natural_freq_max_units: str @@ -454,6 +458,249 @@ def update_harmonic_vibe_props( LOG.error(str(e)) raise e + def get_mechanical_shock_input_fields( + self, model_source=SherlockAnalysisService_pb2.ModelSource.GENERATED + ): + """Get mechanical shock property fields based on the user configuration. + + Parameters + ---------- + model_source : ModelSource + Model source to get the random vibe property fields from. Default is ``GENERATED``. + + Returns + ------- + list + List of mechanical shock property fields based on the user configuration. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> sherlock.project.import_odb_archive( + "ODB++ Tutorial.tgz", + True, + True, + True, + True, + project="Test", + cca_name="Card", + ) + >>> sherlock.analysis.get_mechanical_shock_input_fields() + """ + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + message = SherlockAnalysisService_pb2.GetMechanicalShockInputFieldsRequest( + modelSource=model_source + ) + response = self.stub.getMechanicalShockInputFields(message) + + fields = self._translate_field_names(response.fieldName) + LOG.info(fields) + + return fields + + def update_mechanical_shock_props( + self, + project, + mechanical_shock_properties, + ): + """Update properties for a mechanical shock analysis. + + Parameters + ---------- + project : str + Name of the Sherlock project. + mechanical_shock_properties : list + List of mechanical shock properties for a CCA consisting of these properties: + + - cca_name : str + Name of the CCA. + - model_source: ModelSource, optional + Model source. The default is ``None``. + - shock_result_count : int + Number of mechanical shock result layers to generate. + - critical_shock_strain: float + Critical shock strain. The default is ``None``. + - critical_shock_strain_units: str + Critical shock strain units. The default is ``None``. + Options are ``"strain"``, ``"ε"``, and ``"µε"``. + - part_validation_enabled: bool + Whether to enable part validation. The default is ``None``. + - require_material_assignment_enabled: bool + Whether to require material assignment. The default is ``None``. + - force_model_rebuild: str + How to handle rebuilding of the model. The default is ``None``. + Options are ``"FORCE"`` and ``"AUTO"``. + - natural_freq_min: int + Minimum frequency. The default is ``None``. + - natural_freq_min_units: str + Minimum frequency units. The default is ``None``. + Options are ``"HZ"``, ``"KHZ"``, ``"MHZ"``, and ``"GHZ"``. + - natural_freq_max: int + Maximum frequency. The default is ``None``. + - natural_freq_max_units: str + Maximum frequency units. The default is ``None``. + Options are ``"HZ"``, ``"KHZ"``, ``"MHZ"``, and ``"GHZ"``. + - analysis_temp: double + Temperature. The default is ``None``. + - analysis_temp_units: str + Temperature units. The default is ``None``. + Options are ``"C"``, ``"F"``, and ``"K"``. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> sherlock.project.import_odb_archive( + "ODB++ Tutorial.tgz", + True, + True, + True, + True, + project="Test", + cca_name="Card", + ) + >>> sherlock.analysis.update_mechanical_shock_props( + "Test", + [{ + 'cca_name': 'Card', + 'model_source': ModelSource.GENERATED, + 'shock_result_count': 2, + 'critical_shock_strain': 10, + 'critical_shock_strain_units': 'strain', + 'part_validation_enabled': True, + 'require_material_assignment_enabled': False, + 'force_model_rebuild': 'AUTO', + 'natural_freq_min': 10, + 'natural_freq_min_units': 'Hz', + 'natural_freq_max': 100, + 'natural_freq_max_units': 'KHz', + 'analysis_temp': 20, + 'analysis_temp_units': 'F', + }, + ] + ) + + """ + try: + if project == "": + raise SherlockUpdateMechanicalShockPropsError(message="Project name is invalid.") + + if not isinstance(mechanical_shock_properties, list): + raise SherlockUpdateMechanicalShockPropsError( + message="Mechanical shock properties argument is invalid." + ) + + if len(mechanical_shock_properties) == 0: + raise SherlockUpdateMechanicalShockPropsError( + message="One or more mechanical shock properties are required." + ) + + request = SherlockAnalysisService_pb2.UpdateMechanicalShockPropsRequest(project=project) + + for i, mechanical_shock_props in enumerate(mechanical_shock_properties): + if not isinstance(mechanical_shock_props, dict): + raise SherlockUpdateMechanicalShockPropsError( + f"Mechanical shock props argument is " + f"invalid for mechanical shock properties {i}." + ) + + if "cca_name" not in mechanical_shock_props.keys(): + raise SherlockUpdateMechanicalShockPropsError( + message=f"CCA name is invalid for mechanical shock properties {i}." + ) + + cca_name = mechanical_shock_props["cca_name"] + if cca_name == "": + raise SherlockUpdateMechanicalShockPropsError( + message=f"CCA name is invalid for mechanical shock properties {i}." + ) + + model_source = mechanical_shock_props.get("model_source", None) + shock_result_count = mechanical_shock_props.get("shock_result_count", None) + critical_shock_strain = mechanical_shock_props.get("critical_shock_strain", None) + critical_shock_strain_units = mechanical_shock_props.get( + "critical_shock_strain_units", None + ) + part_validation_enabled = mechanical_shock_props.get( + "part_validation_enabled", None + ) + require_material_assignment_enabled = mechanical_shock_props.get( + "require_material_assignment_enabled", None + ) + force_model_rebuild = mechanical_shock_props.get("force_model_rebuild", None) + natural_freq_min = mechanical_shock_props.get("natural_freq_min", None) + natural_freq_min_units = mechanical_shock_props.get("natural_freq_min_units", None) + natural_freq_max = mechanical_shock_props.get("natural_freq_max", None) + natural_freq_max_units = mechanical_shock_props.get("natural_freq_max_units", None) + analysis_temp = mechanical_shock_props.get("analysis_temp", None) + analysis_temp_units = mechanical_shock_props.get("analysis_temp_units", None) + + props_request = request.mechanicalShockProperties.add() + props_request.ccaName = cca_name + props_request.modelSource = model_source + + if shock_result_count is not None: + props_request.shockResultCount = shock_result_count + + if critical_shock_strain is not None: + props_request.criticalShockStrain = critical_shock_strain + + if critical_shock_strain_units is not None: + props_request.criticalShockStrainUnits = critical_shock_strain_units + + if part_validation_enabled is not None: + props_request.partValidationEnabled = part_validation_enabled + + if require_material_assignment_enabled is not None: + props_request.requireMaterialAssignmentEnabled = ( + require_material_assignment_enabled + ) + + if force_model_rebuild is not None: + props_request.forceModelRebuild = force_model_rebuild + + if natural_freq_min is not None: + props_request.naturalFreqMin = natural_freq_min + + if natural_freq_min_units is not None: + props_request.naturalFreqMinUnits = natural_freq_min_units + + if natural_freq_max is not None: + props_request.naturalFreqMax = natural_freq_max + + if natural_freq_max_units is not None: + props_request.naturalFreqMaxUnits = natural_freq_max_units + + if analysis_temp is not None: + props_request.analysisTemp = analysis_temp + + if analysis_temp_units is not None: + props_request.analysisTempUnits = analysis_temp_units + + except SherlockUpdateMechanicalShockPropsError as e: + LOG.error(str(e)) + raise e + + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + response = self.stub.updateMechanicalShockProps(request) + + try: + if response.value == -1: + raise SherlockUpdateMechanicalShockPropsError(response.message) + else: + LOG.info(response.message) + return response.value + except SherlockUpdateMechanicalShockPropsError as e: + LOG.error(str(e)) + raise e + def get_random_vibe_input_fields(self, model_source=None): """Get random vibe property fields based on the user configuration. diff --git a/src/ansys/sherlock/core/errors.py b/src/ansys/sherlock/core/errors.py index 7777f63e..222486ac 100644 --- a/src/ansys/sherlock/core/errors.py +++ b/src/ansys/sherlock/core/errors.py @@ -808,3 +808,15 @@ def __init__(self, message): def __str__(self): """Format error message.""" return f"Update harmonic vibe properties error: {self.message}" + + +class SherlockUpdateMechanicalShockPropsError(Exception): + """Contains the error raised when properties for mechanical shock analysis cannot be updated.""" + + def __init__(self, message): + """Initialize error message.""" + self.message = message + + def __str__(self): + """Format error message.""" + return f"Update mechanical shock properties error: {self.message}" diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 2b881648..09fafc23 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -1,6 +1,11 @@ # © 2023 ANSYS, Inc. All rights reserved import time +try: + import SherlockAnalysisService_pb2 +except ModuleNotFoundError: + from ansys.api.sherlock.v0 import SherlockAnalysisService_pb2 + import grpc import pytest @@ -9,6 +14,7 @@ SherlockRunAnalysisError, SherlockRunStrainMapAnalysisError, SherlockUpdateHarmonicVibePropsError, + SherlockUpdateMechanicalShockPropsError, SherlockUpdateNaturalFrequencyPropsError, SherlockUpdatePcbModelingPropsError, SherlockUpdateRandomVibePropsError, @@ -33,9 +39,11 @@ def test_all(): time.sleep(1) helper_test_run_strain_map_analysis(analysis) helper_test_get_harmonic_vibe_input_fields(analysis) + helper_test_get_mechanical_shock_input_fields(analysis) helper_test_get_random_vibe_input_fields(analysis) helper_test_translate_field_names(analysis) helper_test_update_harmonic_vibe_props(analysis) + helper_test_update_mechanical_shock_props(analysis) helper_test_update_random_vibe_props(analysis) helper_test_get_natural_frequency_input_fields(analysis) helper_test_update_natural_frequency_props(analysis) @@ -429,6 +437,22 @@ def helper_test_get_harmonic_vibe_input_fields(analysis): assert "require_material_assignment_enabled" in fields +def helper_test_get_mechanical_shock_input_fields(analysis): + if analysis._is_connection_up(): + fields = analysis.get_mechanical_shock_input_fields() + assert "shock_result_count" in fields + assert "critical_strain_shock" in fields + assert "critical_strain_shock_units" in fields + assert "part_validation_enabled" in fields + assert "require_material_assignment_enabled" in fields + assert "analysis_temp" in fields + assert "analysis_temp_units" in fields + assert "natural_freq_min" in fields + assert "natural_freq_min_units" in fields + assert "natural_freq_max" in fields + assert "natural_freq_max_units" in fields + + def helper_test_get_random_vibe_input_fields(analysis): if analysis._is_connection_up(): fields = analysis.get_random_vibe_input_fields() @@ -665,6 +689,170 @@ def helper_test_update_harmonic_vibe_props(analysis): pytest.fail(str(e)) +def helper_test_update_mechanical_shock_props(analysis): + try: + analysis.update_mechanical_shock_props( + "", + [ + { + "cca_name": "Main Board", + "shock_result_count": 2, + "critical_shock_strain": 10, + "critical_shock_strain_units": "strain", + "part_validation_enabled": True, + "require_material_assignment_enabled": False, + "force_model_rebuild": "AUTO", + "natural_freq_min": 10, + "natural_freq_min_units": "Hz", + "natural_freq_max": 100, + "natural_freq_max_units": "KHz", + "analysis_temp": 20, + "analysis_temp_units": "F", + }, + ], + ) + assert False + except SherlockUpdateMechanicalShockPropsError as e: + assert str(e) == "Update mechanical shock properties error: Project name is invalid." + + try: + analysis.update_mechanical_shock_props("Test", "INVALID_TYPE") + assert False + except SherlockUpdateMechanicalShockPropsError as e: + assert ( + str(e) == "Update mechanical shock properties error: " + "Mechanical shock properties argument is invalid." + ) + + try: + analysis.update_mechanical_shock_props("Test", []) + assert False + except SherlockUpdateMechanicalShockPropsError as e: + assert ( + str(e) == "Update mechanical shock properties error: " + "One or more mechanical shock properties are required." + ) + + try: + analysis.update_mechanical_shock_props("Test", ["INVALID"]) + assert False + except SherlockUpdateMechanicalShockPropsError as e: + assert ( + str(e) == "Update mechanical shock properties error: " + "Mechanical shock props argument is invalid for mechanical shock properties 0." + ) + + try: + analysis.update_mechanical_shock_props( + "Tutorial Project", + [ + { + "shock_result_count": 2, + "critical_shock_strain": 10, + "critical_shock_strain_units": "strain", + "part_validation_enabled": True, + "require_material_assignment_enabled": False, + "force_model_rebuild": "AUTO", + "natural_freq_min": 10, + "natural_freq_min_units": "Hz", + "natural_freq_max": 100, + "natural_freq_max_units": "KHz", + "analysis_temp": 20, + "analysis_temp_units": "F", + }, + ], + ) + assert False + except SherlockUpdateMechanicalShockPropsError as e: + assert ( + str(e) == "Update mechanical shock properties error: " + "CCA name is invalid for mechanical shock properties 0." + ) + + try: + analysis.update_mechanical_shock_props( + "Tutorial Project", + [ + { + "cca_name": "", + "shock_result_count": 2, + "critical_shock_strain": 10, + "critical_shock_strain_units": "strain", + "part_validation_enabled": True, + "require_material_assignment_enabled": False, + "force_model_rebuild": "AUTO", + "natural_freq_min": 10, + "natural_freq_min_units": "Hz", + "natural_freq_max": 100, + "natural_freq_max_units": "KHz", + "analysis_temp": 20, + "analysis_temp_units": "F", + }, + ], + ) + assert False + except SherlockUpdateMechanicalShockPropsError as e: + assert ( + str(e) == "Update mechanical shock properties error: " + "CCA name is invalid for mechanical shock properties 0." + ) + + if not analysis._is_connection_up(): + return + + try: + analysis.update_mechanical_shock_props( + "Tutorial Project", + [ + { + "cca_name": "Main Board", + "model_source": SherlockAnalysisService_pb2.ModelSource.GENERATED, + "shock_result_count": 2, + "critical_shock_strain": 10, + "critical_shock_strain_units": "INVALID", + "part_validation_enabled": True, + "require_material_assignment_enabled": False, + "force_model_rebuild": "AUTO", + "natural_freq_min": 10, + "natural_freq_min_units": "Hz", + "natural_freq_max": 100, + "natural_freq_max_units": "KHz", + "analysis_temp": 20, + "analysis_temp_units": "F", + }, + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockUpdateMechanicalShockPropsError + + try: + result = analysis.update_mechanical_shock_props( + "Tutorial Project", + [ + { + "cca_name": "Main Board", + "model_source": SherlockAnalysisService_pb2.ModelSource.GENERATED, + "shock_result_count": 2, + "critical_shock_strain": 10, + "critical_shock_strain_units": "strain", + "part_validation_enabled": True, + "require_material_assignment_enabled": False, + "force_model_rebuild": "AUTO", + "natural_freq_min": 10, + "natural_freq_min_units": "Hz", + "natural_freq_max": 100, + "natural_freq_max_units": "KHz", + "analysis_temp": 20, + "analysis_temp_units": "F", + }, + ], + ) + assert result == 0 + except SherlockUpdateMechanicalShockPropsError as e: + pytest.fail(str(e)) + + def helper_test_update_random_vibe_props(analysis): try: analysis.update_random_vibe_props( From 2a45fcde1ac1b37d64d4f23e285454501b4a15ad Mon Sep 17 00:00:00 2001 From: Jeff Moody <110494049+ansys-jmoody@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:48:37 -0500 Subject: [PATCH 04/27] Document API return values (#133) --- src/ansys/sherlock/core/analysis.py | 60 ++++++++++------- src/ansys/sherlock/core/common.py | 18 ++++- src/ansys/sherlock/core/launcher.py | 12 +++- src/ansys/sherlock/core/lifecycle.py | 99 +++++++++++++++++++++++----- src/ansys/sherlock/core/model.py | 13 +++- src/ansys/sherlock/core/parts.py | 32 ++++++++- src/ansys/sherlock/core/project.py | 40 ++++++++++- src/ansys/sherlock/core/stackup.py | 56 +++++++++++++--- tests/test_analysis.py | 72 ++++++++++++-------- tests/test_model.py | 11 +++- 10 files changed, 328 insertions(+), 85 deletions(-) diff --git a/src/ansys/sherlock/core/analysis.py b/src/ansys/sherlock/core/analysis.py index d527f07a..dae23612 100644 --- a/src/ansys/sherlock/core/analysis.py +++ b/src/ansys/sherlock/core/analysis.py @@ -164,9 +164,16 @@ def run_analysis( LOG.error(str(e)) raise e - def get_harmonic_vibe_input_fields(self): + def get_harmonic_vibe_input_fields(self, model_source=None): """Get harmonic vibe property fields based on the user configuration. + Parameters + ---------- + model_source : ModelSource, optional + Model source to get the harmonic vibe property fields from. + Only ModelSource.GENERATED is supported. + The default is ``None``. + Returns ------- list @@ -185,14 +192,14 @@ def get_harmonic_vibe_input_fields(self): project="Test", cca_name="Card", ) - >>> sherlock.analysis.get_harmonic_vibe_input_fields() + >>> sherlock.analysis.get_harmonic_vibe_input_fields(ModelSource.GENERATED) """ if not self._is_connection_up(): LOG.error("There is no connection to a gRPC service.") return message = SherlockAnalysisService_pb2.GetHarmonicVibeInputFieldsRequest( - modelSource="GENERATED" + modelSource=model_source ) response = self.stub.getHarmonicVibeInputFields(message) @@ -226,7 +233,7 @@ def update_harmonic_vibe_props( Whether to enable part validation. The default is ``None``. - require_material_assignment_enabled: bool Whether to require material assignment. The default is ``None``. - - analysis_temp: double + - analysis_temp: float Temperature. The default is ``None``. - analysis_temp_units: str Temperature units. The default is ``None``. @@ -255,6 +262,11 @@ def update_harmonic_vibe_props( Whether to reuse the natural frequency for modal analysis. The default is ``None``. This parameter is for NX Nastran analysis only. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -458,15 +470,15 @@ def update_harmonic_vibe_props( LOG.error(str(e)) raise e - def get_mechanical_shock_input_fields( - self, model_source=SherlockAnalysisService_pb2.ModelSource.GENERATED - ): + def get_mechanical_shock_input_fields(self, model_source=None): """Get mechanical shock property fields based on the user configuration. Parameters ---------- - model_source : ModelSource - Model source to get the random vibe property fields from. Default is ``GENERATED``. + model_source : ModelSource, optional + Model source to get the random vibe property fields from. + Only ModelSource.GENERATED is supported. + Default is ``None``. Returns ------- @@ -486,7 +498,7 @@ def get_mechanical_shock_input_fields( project="Test", cca_name="Card", ) - >>> sherlock.analysis.get_mechanical_shock_input_fields() + >>> sherlock.analysis.get_mechanical_shock_input_fields(ModelSource.GENERATED) """ if not self._is_connection_up(): LOG.error("There is no connection to a gRPC service.") @@ -550,6 +562,11 @@ def update_mechanical_shock_props( Temperature units. The default is ``None``. Options are ``"C"``, ``"F"``, and ``"K"``. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -708,6 +725,7 @@ def get_random_vibe_input_fields(self, model_source=None): ---------- model_source : ModelSource, optional Model source to get the random vibe property fields from. + The default is ``None``. Returns ------- @@ -727,9 +745,7 @@ def get_random_vibe_input_fields(self, model_source=None): project="Test", cca_name="Card", ) - >>> sherlock.analysis.get_random_vibe_input_fields( - model_source=ModelSource.STRAIN_MAP - ) + >>> sherlock.analysis.get_random_vibe_input_fields(ModelSource.STRAIN_MAP) """ if not self._is_connection_up(): LOG.error("There is no connection to a gRPC service.") @@ -746,9 +762,9 @@ def get_random_vibe_input_fields(self, model_source=None): return fields def _translate_field_names(self, names_list): - names = "" + names = [] for name in list(names_list): - names = names + "\n" + self.FIELD_NAMES.get(name) + names.append(self.FIELD_NAMES.get(name)) return names @@ -782,21 +798,21 @@ def update_random_vibe_props( random_vibe_damping: str, optional One or more modal damping ratios. The default is ``None``. Separate multiple float values with commas. - natural_freq_min: double, optional + natural_freq_min: float, optional Minimum frequency. The default is ``None``. This parameter is for NX Nastran analysis only. natural_freq_min_units: str, optional Minimum frequency units. The default is ``None``. Options are ``"HZ"``, ``"KHZ"``, ``"MHZ"``, and ``"GHZ"``. This parameter is for NX Nastran analysis only. - natural_freq_max: double, optional + natural_freq_max: float, optional Maximum frequency. The default is ``None``. This parameter is for NX Nastran analysis only. natural_freq_max_units: str, optional Maximum frequency units. The default is ``None``. Options are ``"HZ"``, ``"KHZ"``, ``"MHZ"``, and ``"GHZ"``. This parameter is for NX Nastran analysis only. - analysis_temp: double, optional + analysis_temp: float, optional Temperature. The default is ``None``. analysis_temp_units: str, optional Temperature units. The default is ``None``. @@ -815,7 +831,7 @@ def update_random_vibe_props( require_material_assignment_enabled: bool, optional Whether to require material assignment. The default is ``None``. model_source: ModelSource, optional - Model source. + Model source. The default is ``None``. This parameter is required for strain map analysis. strain_map_natural_freqs : list, optional List of natural frequencies. The default is ``None``. @@ -963,12 +979,12 @@ def update_natural_frequency_props( Name of the CCA. natural_freq_count: int Natural frequecy result count. - natural_freq_min: double, optional + natural_freq_min: float, optional Minimum frequency. This parameter is for NX Nastran analysis only. natural_freq_min_units: str, optional Minimum frequency units. Options are ``"HZ"``, ``"KHZ"``, ``"MHZ"``, and ``"GHZ"``. This parameter is for NX Nastran analysis only. - natural_freq_max: double, optional + natural_freq_max: float, optional Maximum frequency. This parameter is for NX Nastran analysis only. natural_freq_max_units: str, optional Maximum frequency units. Options are ``"HZ"``, ``"KHZ"``, ``"MHZ"``, and ``"GHZ"``. @@ -977,7 +993,7 @@ def update_natural_frequency_props( Whether part validation is enabled. require_material_assignment_enabled: bool Whether to require material assignment. - analysis_temp: double, optional + analysis_temp: float, optional Temperature. analysis_temp_units: str, optional Temperature units. Options are ``"C"``, ``"F"``, and ``"K"``. diff --git a/src/ansys/sherlock/core/common.py b/src/ansys/sherlock/core/common.py index a6770d39..7da9c493 100644 --- a/src/ansys/sherlock/core/common.py +++ b/src/ansys/sherlock/core/common.py @@ -22,7 +22,14 @@ def __init__(self, channel): self.stub = SherlockCommonService_pb2_grpc.SherlockCommonServiceStub(channel) def check(self): - """Perform a health check on the gRPC connection.""" + """Perform a health check on the gRPC connection. + + Returns + ------- + bool + Whether the Sherlock client is connected via gRPC. + + """ if not self._is_connection_up(): LOG.error("Health check failed.") return False @@ -31,7 +38,14 @@ def check(self): return True def is_sherlock_client_loading(self): - """Check if the Sherlock client is opened and still initializing.""" + """Check if the Sherlock client is opened and done initializing. + + Returns + ------- + bool + Whether the Sherlock client is opened and done initializing. + + """ if not self._is_connection_up(): LOG.error("There is no connection to a gRPC service.") return diff --git a/src/ansys/sherlock/core/launcher.py b/src/ansys/sherlock/core/launcher.py index a17720b2..68d8dc10 100644 --- a/src/ansys/sherlock/core/launcher.py +++ b/src/ansys/sherlock/core/launcher.py @@ -50,6 +50,11 @@ def launch_sherlock( sherlock_cmd_args : str, optional Additional command arguments for launching Sherlock. The default is ``""``. + Returns + ------- + Sherlock + The instance of sherlock. + Examples -------- >>> from ansys.sherlock.core import launcher @@ -105,7 +110,12 @@ def launch_sherlock( def connect_grpc_channel(port=SHERLOCK_DEFAULT_PORT): """Create a gRPC connection to a specified port and return the ``sherlock``connection object. - The ``sherlock``connecton object is used to invoke the APIs from their respective services. + The ``sherlock``connection object is used to invoke the APIs from their respective services. + + Returns + ------- + Sherlock + The instance of sherlock. """ channel_param = f"{LOCALHOST}:{port}" channel = grpc.insecure_channel(channel_param) diff --git a/src/ansys/sherlock/core/lifecycle.py b/src/ansys/sherlock/core/lifecycle.py index 089d6140..a8b6c9f0 100644 --- a/src/ansys/sherlock/core/lifecycle.py +++ b/src/ansys/sherlock/core/lifecycle.py @@ -298,6 +298,11 @@ def create_life_phase( description : str, optional Description of the life phase. The default is ``""``. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -419,6 +424,11 @@ def add_random_vibe_event( description : str, optional Description of the random vibe event. The default is ``""``. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -559,6 +569,11 @@ def add_random_vibe_profiles( - amplitude : double Amplitude of the profile entry expressed in amplitude units. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -712,7 +727,7 @@ def add_thermal_event( Name of the life cycle phase to add the thermal event to. event_name : str Name of the thermal event. - num_of_cycles : double + num_of_cycles : float Number of cycles for the thermal event. cycle_type : str Cycle type. Options are ``"COUNT"``, ``"DUTY_CYCLE"``, ``"PER_YEAR"``, @@ -722,6 +737,11 @@ def add_thermal_event( description : str, optional Description of the thermal event. The default is ``""``. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -836,11 +856,15 @@ def add_thermal_profiles( Name of the thermal step. - type : str Type of the thermal step. Options are ``"HOLD"`` and ``"RAMP"``. - - time : double + - time : float Duration of the thermal step expressed in time units. - - temperature : double + - temperature : float Temperature of the step expressed in temperature units. + Returns + ------- + int + Status code of the response. 0 for success. Examples -------- @@ -991,17 +1015,17 @@ def add_harmonic_event( Name of the life cycle phase to add the harmonic event to. event_name : str Name of the harmonic event. - duration : double + duration : float Event duration length. duration_units : str Event duration units. Options are ``"ms"``, ``"sec"``, ``"min"``, ``"hr"``, ``"day"``, and ``"year"``. - num_of_cycles : double + num_of_cycles : float Number of cycles for the harmonic event. cycle_type : str Cycle type. Options are ``"COUNT"``, ``"DUTY_CYCLE"``, ``"PER_YEAR"``, ``"PER_DAY"``, ``"PER_HOUR"``, ``"PER_MIN"``, and ``"PER_SEC"``. - sweep_rate : double + sweep_rate : float Sweep rate for the harmonic event. orientation : str PCB orientation in the format of ``"azimuth, elevation"``. For example, @@ -1013,6 +1037,11 @@ def add_harmonic_event( description : str, optional Description of the harmonic event. The default is ``""``. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -1151,15 +1180,20 @@ def add_harmonic_vibe_profiles( - harmonic_profile_entries : list List of harmonic profile entries consisting of these properties: - - frequency : double + - frequency : float Frequency of the harmonic profile expressed in frequency units. - - load : double + - load : float Load of the harmonic profile expressed in load units. - triaxial_axis : str Axis that this profile should be assigned to if the harmonic profile type is ``"Triaxial"``. Options are: ``"x"``, ``"y"``, and ``"z"``. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -1318,12 +1352,12 @@ def add_shock_event( Name of the life cycle phase to add this shock event to. event_name : str Name of the shock event. - duration : double + duration : float Event duration length. duration_units : str Event duration units. Options are ``"ms"``, ``"sec"``, ``"min"``, ``"hr"``, ``"day"``, and ``"year"``. - num_of_cycles : double + num_of_cycles : float Number of cycles for the shock event. cycle_type : str Cycle type. Options are ``"COUNT"``, ``"DUTY CYCLE"``, @@ -1336,6 +1370,11 @@ def add_shock_event( description : str, optional Description of the shock event. The default is ``""``. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -1446,12 +1485,12 @@ def add_shock_profiles( Name of the shock event. - profile_name : str Name of the shock profile. - - duration : double + - duration : float Pulse duration. - duration_units : str Pulse duration units. Options are ``"ms"``, ``"sec"``, ``"min"``, ``"hr"``, ``"day"``, and ``"year"``. - - sample_rate : double + - sample_rate : float Sample rate. - sample_rate_units : str Sample rate units. Options are ``"ms"``, ``"sec"``, ``"min"``, ``"hr"``, @@ -1468,13 +1507,18 @@ def add_shock_profiles( Shape of the shock profile entry. Options are ``"FullSine"``, ``"HalfSine"``, ``"Haversine"``, ``"Triangle"``, ``"Sawtooth"``, ``"FullSquare"``, and ``"HalfSquare"``. - - load : double + - load : float Load of the profile entry expressed in load units. - - freq : double + - freq : float Frequency of the profile entry expressed in frequency units. - - decay : double + - decay : float Decay value of the profile entry. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -1636,6 +1680,11 @@ def load_random_vibe_profile(self, project, phase_name, event_name, file_path): file_path : str File path for thermal profile .dat or .csv file + Returns + ------- + int + Status code of the response. 0 for success. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -1702,6 +1751,11 @@ def load_thermal_profile(self, project, phase_name, event_name, file_path): file_path : str File path for thermal profile .dat or .csv file + Returns + ------- + int + Status code of the response. 0 for success. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -1765,6 +1819,11 @@ def load_harmonic_profile(self, project, phase_name, event_name, file_path): file_path : str Path for DAT or CSV file with the harmonic profile. + Returns + ------- + int + Status code of the response. 0 for success. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -1832,6 +1891,11 @@ def load_shock_profile_dataset(self, project, phase_name, event_name, file_path) file_path : str File path for thermal profile .dat or .csv file + Returns + ------- + int + Status code of the response. 0 for success. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -1890,6 +1954,11 @@ def load_shock_profile_pulses(self, project, phase_name, event_name, file_path): file_path : str Path for thermal profile .dat or .csv file + Returns + ------- + int + Status code of the response. 0 for success. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock diff --git a/src/ansys/sherlock/core/model.py b/src/ansys/sherlock/core/model.py index ec61421f..8dcad7bb 100644 --- a/src/ansys/sherlock/core/model.py +++ b/src/ansys/sherlock/core/model.py @@ -106,6 +106,11 @@ def export_trace_reinforcement_model( Units associated with the maximum segment for representing round drill holes by a polygon. The default is ``"mm"``. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core import launcher @@ -172,7 +177,7 @@ def export_trace_reinforcement_model( if return_code.value != 0: raise SherlockModelServiceError(return_code.message) - return return_code.value, return_code.message + return return_code.value except Exception as e: LOG.error(str(e)) raise @@ -230,6 +235,11 @@ def generate_trace_model( the layer exists, the snapshot image is used. Otherwise, an image is created in the same way as a snapshot image is created. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core import launcher @@ -239,7 +249,6 @@ def generate_trace_model( 'Tutorial Project', 'Main Board', 0.05, 'mm' 0.0, 'mm2', 0.0, 'mm2') - """ try: if not project_name: diff --git a/src/ansys/sherlock/core/parts.py b/src/ansys/sherlock/core/parts.py index 73de5f0f..320cd966 100644 --- a/src/ansys/sherlock/core/parts.py +++ b/src/ansys/sherlock/core/parts.py @@ -172,6 +172,11 @@ def update_parts_list( duplication : UpdatesPartsListRequestDuplicationMode How to handle duplication during the update. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -258,6 +263,11 @@ def update_parts_locations(self, project, cca_name, part_loc): - mirrored : str Mirrored. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -342,6 +352,11 @@ def update_parts_locations_by_file(self, project, cca_name, file_path, numeric_f ``"English (United States)"`` is the numeric format. This indicates that points are used as decimal markers. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -417,6 +432,11 @@ def import_parts_list(self, project, cca_name, import_file, import_as_user_src): Whether to set the data source of the properties to ``"User"``. Otherwise, the data source is set to the name of the CSV file. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -466,7 +486,7 @@ def import_parts_list(self, project, cca_name, import_file, import_as_user_src): raise SherlockImportPartsListError(response.message) LOG.info(response.message) - return + return response.value except SherlockImportPartsListError as e: LOG.error(str(e)) raise e @@ -483,6 +503,11 @@ def export_parts_list(self, project, cca_name, export_file): export_file : str Full path for the CSV file to export the parts list to. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -543,6 +568,11 @@ def enable_lead_modeling(self, project, cca_name): cca_name : str Name of the CCA. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock diff --git a/src/ansys/sherlock/core/project.py b/src/ansys/sherlock/core/project.py index a509b763..da995043 100644 --- a/src/ansys/sherlock/core/project.py +++ b/src/ansys/sherlock/core/project.py @@ -39,6 +39,11 @@ def delete_project(self, project): project : str Name of the Sherlock project. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -101,6 +106,11 @@ def import_odb_archive( Name of the CCA name. The default is ``None``, in which case the name of the ODB++ archive file is used for the CCA name. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -143,7 +153,7 @@ def import_odb_archive( raise SherlockImportODBError(response.message) LOG.info(response.message) - return + return response.value except SherlockImportODBError as e: LOG.error(str(e)) raise e @@ -168,6 +178,11 @@ def import_ipc2581_archive( Name of the CCA. The default is ``None``, in which case the name of the IPC-2581 archive file is used for the CCA name. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -207,7 +222,7 @@ def import_ipc2581_archive( raise SherlockImportIpc2581Error(response.message) LOG.info(response.message) - return + return response.value except Exception as e: LOG.error(str(e)) raise e @@ -226,6 +241,11 @@ def generate_project_report(self, project, author, company, report_file): report_file: str Full path to where to create the report. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -292,6 +312,11 @@ def list_ccas(self, project, cca_names=None): List of CCA names. The default is ``None``, in which case all CCAs in the project are returned. + Returns + ------- + list + CCAs and subassembly CCAs. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -358,6 +383,11 @@ def add_strain_maps(self, project, strain_maps): List of CCA names to assign the file to. When no list is specified, the file is assigned to all CCAs in the project. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -453,6 +483,7 @@ def add_strain_maps(self, project, strain_maps): raise SherlockAddStrainMapsError(message=return_code.message) + return return_code.value except SherlockAddStrainMapsError as e: for error in e.str_itr(): LOG.error(error) @@ -469,6 +500,11 @@ def list_strain_maps(self, project, cca_names=None): List of CCA names to provide strain maps for. The default is ``None``, in which case all CCAs in the project are returned. + Returns + ------- + list + All strain maps or strain maps for the specified CCAs. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock diff --git a/src/ansys/sherlock/core/stackup.py b/src/ansys/sherlock/core/stackup.py index 8756d720..1a6a38ac 100644 --- a/src/ansys/sherlock/core/stackup.py +++ b/src/ansys/sherlock/core/stackup.py @@ -172,7 +172,7 @@ def gen_stackup( Name of the Sherlock project. cca_name : str Name of the CCA. - board_thickness : double + board_thickness : float Board thickness. board_thickness_unit : str Units for the board thickness. @@ -184,21 +184,26 @@ def gen_stackup( Material for the PCB. conductor_layers_cnt : int32 Number of conductor layers. - signal_layer_thickness : double + signal_layer_thickness : float Signal layer thickness. signal_layer_thickness_unit : str Units for the signal layer thickness. - min_laminate_thickness : double + min_laminate_thickness : float Minimum thickness of laminate layers. min_laminate_thickness_unit : str Units for the minimum thickness of laminate layers. maintain_symmetry : bool Whether to maintain symmetry. - power_layer_thickness : double + power_layer_thickness : float Power layer thickness. power_layer_thickness_unit : str Units for the power layer thickness. + Returns + ------- + int + Status code of the response. 0 for success. + Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -317,7 +322,7 @@ def update_conductor_layer( ``"SIGNAL"``, ``"POWER"``, or ``"SUBSTRATE"``. material : str, optional Conductor material. The default is ``""``. - thickness : double, optional + thickness : float, optional Conductor layer thickness. The default is ``0``. thickness_unit : str, optional Units for the conductor layer thickness. The @@ -331,6 +336,11 @@ def update_conductor_layer( ---- Using the default value for a property causes no changes for that property. + Returns + ------- + int + Status code of the response. 0 for success. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -456,7 +466,7 @@ def update_laminate_layer( Material grade. The default is ``""``. material : str, optional Material name. The default is ``""``. - thickness : double, optional + thickness : float, optional Laminate thickness. The default is ``0``. thickness_unit : str, optional Units for the laminate thickness. The default is ``""``. @@ -468,9 +478,9 @@ def update_laminate_layer( - style : str Style of the glass construction. - - resinPercentage : double + - resinPercentage : float Resin percentage. - - thickness: double + - thickness: float Thickness. - thicknessUnit: str Units for the thickness. @@ -487,6 +497,11 @@ def update_laminate_layer( ---- Using the default value for a property causes no changes for that property. + Returns + ------- + int + Status code of the response. 0 for success. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -606,6 +621,11 @@ def list_conductor_layers(self, project): project : str Name of the Sherlock project. + Returns + ------- + list + The conductor layers of all CCAs in the project. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -657,6 +677,11 @@ def list_laminate_layers(self, project): project : str Name of the Sherlock project. + Returns + ------- + list + The laminate layers of all CCAs in the project. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -715,6 +740,11 @@ def get_layer_count(self, project, cca_name): cca_name : str, required Name of the CCA. + Returns + ------- + int + The number of layers of the CCA in the project. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -764,6 +794,11 @@ def get_stackup_props(self, project, cca_name): cca_name : str, required Name of the CCA. + Returns + ------- + list + The stackup properties of the CCA in the project. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock @@ -816,6 +851,11 @@ def get_total_conductor_thickness(self, project, cca_name, thickness_unit): thickness_unit : str, optional Units for laminate thickness. + Returns + ------- + float + The conductor thickness of the CCA in the specified units. + Example ------- >>> from ansys.sherlock.core.launcher import launch_sherlock diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 09fafc23..47f962c9 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -428,8 +428,13 @@ def helper_test_run_strain_map_analysis(analysis): def helper_test_get_harmonic_vibe_input_fields(analysis): if analysis._is_connection_up(): fields = analysis.get_harmonic_vibe_input_fields() - assert "analysis_temp" in fields - assert "analysis_temp_units" in fields + assert "harmonic_vibe_count" in fields + assert "harmonic_vibe_damping" in fields + assert "part_validation_enabled" in fields + assert "require_material_assignment_enabled" in fields + assert "model_source" not in fields + + fields = analysis.get_harmonic_vibe_input_fields(ModelSource.GENERATED) assert "harmonic_vibe_count" in fields assert "harmonic_vibe_damping" in fields assert "model_source" in fields @@ -445,8 +450,19 @@ def helper_test_get_mechanical_shock_input_fields(analysis): assert "critical_strain_shock_units" in fields assert "part_validation_enabled" in fields assert "require_material_assignment_enabled" in fields - assert "analysis_temp" in fields - assert "analysis_temp_units" in fields + assert "natural_freq_min" in fields + assert "natural_freq_min_units" in fields + assert "natural_freq_max" in fields + assert "natural_freq_max_units" in fields + assert "model_source" not in fields + + fields = analysis.get_mechanical_shock_input_fields(ModelSource.GENERATED) + assert "shock_result_count" in fields + assert "critical_strain_shock" in fields + assert "critical_strain_shock_units" in fields + assert "model_source" in fields + assert "part_validation_enabled" in fields + assert "require_material_assignment_enabled" in fields assert "natural_freq_min" in fields assert "natural_freq_min_units" in fields assert "natural_freq_max" in fields @@ -456,15 +472,12 @@ def helper_test_get_mechanical_shock_input_fields(analysis): def helper_test_get_random_vibe_input_fields(analysis): if analysis._is_connection_up(): fields = analysis.get_random_vibe_input_fields() - assert "analysis_temp" in fields - assert "analysis_temp_units" in fields assert "part_validation_enabled" in fields assert "random_vibe_damping" in fields assert "require_material_assignment_enabled" in fields + assert "model_source" not in fields fields = analysis.get_random_vibe_input_fields(ModelSource.GENERATED) - assert "analysis_temp" in fields - assert "analysis_temp_units" in fields assert "model_source" in fields assert "part_validation_enabled" in fields assert "random_vibe_damping" in fields @@ -506,27 +519,28 @@ def helper_test_translate_field_names(analysis): ] ) - expected = """ -analysis_temp -analysis_temp -analysis_temp_units -analysis_temp_units -filter_by_event_frequency -force_model_rebuild -harmonic_vibe_damping -harmonic_vibe_count -model_source -natural_freq_count -natural_freq_min -natural_freq_min_units -natural_freq_max -natural_freq_max_units -part_validation_enabled -perform_nf_freq_range_check -random_vibe_damping -require_material_assignment_enabled -reuse_modal_analysis -strain_map_natural_freqs""" + expected = [ + "analysis_temp", + "analysis_temp", + "analysis_temp_units", + "analysis_temp_units", + "filter_by_event_frequency", + "force_model_rebuild", + "harmonic_vibe_damping", + "harmonic_vibe_count", + "model_source", + "natural_freq_count", + "natural_freq_min", + "natural_freq_min_units", + "natural_freq_max", + "natural_freq_max_units", + "part_validation_enabled", + "perform_nf_freq_range_check", + "random_vibe_damping", + "require_material_assignment_enabled", + "reuse_modal_analysis", + "strain_map_natural_freqs", + ] assert results == expected diff --git a/tests/test_model.py b/tests/test_model.py index e1e2898f..2c6abba8 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -24,9 +24,6 @@ def test_model_export_trace_reinforcement_model(self): path = os.path.join(temp_dir, "export.wbjn") if model._is_connection_up(): - result = model.export_trace_reinforcement_model("Tutorial Project", "Main Board", path) - assert result[0] == 0 - try: invalid_cca = "Invalid CCA" model.export_trace_reinforcement_model("Tutorial Project", invalid_cca, path) @@ -34,6 +31,14 @@ def test_model_export_trace_reinforcement_model(self): except Exception as e: assert type(e) == SherlockModelServiceError + try: + result = model.export_trace_reinforcement_model( + "Tutorial Project", "Main Board", path + ) + assert result == 0 + except SherlockModelServiceError as e: + pytest.fail(str(e)) + try: model.export_trace_reinforcement_model("", "Main Board", path) pytest.fail("No exception raised when using an invalid parameter") From 2c3ad4a7a604e69a16e44af58d31c5ae1dd0d287 Mon Sep 17 00:00:00 2001 From: Jeff Moody <110494049+ansys-jmoody@users.noreply.github.com> Date: Fri, 23 Jun 2023 12:34:09 -0500 Subject: [PATCH 05/27] Use classes for returning values to API methods (#134) --- doc/source/api/index.rst | 2 + doc/source/api/parts_types.rst | 13 +++++++ src/ansys/sherlock/core/analysis.py | 14 +++---- src/ansys/sherlock/core/parts.py | 19 ++++++--- src/ansys/sherlock/core/sherlock.py | 1 - src/ansys/sherlock/core/stackup.py | 7 ++-- .../sherlock/core/types/analysis_types.py | 14 +++---- src/ansys/sherlock/core/types/common_types.py | 2 +- src/ansys/sherlock/core/types/parts_types.py | 25 +++++++++++- .../sherlock/core/types/stackup_types.py | 18 +++++++++ tests/test_parts.py | 39 ++++++++++++++----- tests/test_stackup.py | 9 ++++- 12 files changed, 126 insertions(+), 37 deletions(-) create mode 100644 doc/source/api/parts_types.rst create mode 100644 src/ansys/sherlock/core/types/stackup_types.py diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 2adac8b0..e99acab2 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -17,6 +17,7 @@ Use the search feature or click links to view API documentation. lifecycle model parts + parts_types project stackup @@ -29,5 +30,6 @@ Use the search feature or click links to view API documentation. ansys.sherlock.core.lifecycle ansys.sherlock.core.model ansys.sherlock.core.parts + ansys.sherlock.core.types.parts_types ansys.sherlock.core.project ansys.sherlock.core.stackup diff --git a/doc/source/api/parts_types.rst b/doc/source/api/parts_types.rst new file mode 100644 index 00000000..07b52e4f --- /dev/null +++ b/doc/source/api/parts_types.rst @@ -0,0 +1,13 @@ +.. _ref_parts_types: + +Parts Types +=========== + +Constants and classes used for the Parts API. + +.. currentmodule:: ansys.sherlock.core.types.parts_types + +.. autosummary:: + :toctree: _autosummary + + PartLocation diff --git a/src/ansys/sherlock/core/analysis.py b/src/ansys/sherlock/core/analysis.py index dae23612..651e75f8 100644 --- a/src/ansys/sherlock/core/analysis.py +++ b/src/ansys/sherlock/core/analysis.py @@ -55,6 +55,13 @@ def __init__(self, channel): "strainMapNaturalFreqs": "strain_map_natural_freqs", } + def _translate_field_names(self, names_list): + names = [] + for name in list(names_list): + names.append(self.FIELD_NAMES.get(name)) + + return names + @staticmethod def _add_analyses(request, analyses): """Add analyses.""" @@ -761,13 +768,6 @@ def get_random_vibe_input_fields(self, model_source=None): return fields - def _translate_field_names(self, names_list): - names = [] - for name in list(names_list): - names.append(self.FIELD_NAMES.get(name)) - - return names - def update_random_vibe_props( self, project, diff --git a/src/ansys/sherlock/core/parts.py b/src/ansys/sherlock/core/parts.py index 320cd966..e22d05a1 100644 --- a/src/ansys/sherlock/core/parts.py +++ b/src/ansys/sherlock/core/parts.py @@ -20,6 +20,7 @@ SherlockUpdatePartsLocationsError, ) from ansys.sherlock.core.grpc_stub import GrpcStub +from ansys.sherlock.core.types.parts_types import PartLocation class Parts(GrpcStub): @@ -630,7 +631,11 @@ def get_part_location(self, project, cca_name, ref_des, location_units): Reference designator for specific part. location_units: str Valid units for a part's location. - ---------- + + Returns + ------- + list + List of PartLocation objects. Examples -------- @@ -645,13 +650,13 @@ def get_part_location(self, project, cca_name, ref_des, location_units): project="Test", cca_name="Card", ) - >>> part_location = sherlock.parts.get_part_location( + >>> part_locations = sherlock.parts.get_part_location( project="Tutorial", cca_name="Main Board", - ref_des="C1", + ref_des="C1,C2", location_units="in", ) - >>> print(f"{part_location}") + >>> print(f"{part_locations}") """ try: if project == "": @@ -677,7 +682,11 @@ def get_part_location(self, project, cca_name, ref_des, location_units): if return_code.value == -1: raise SherlockGetPartLocationError(return_code.message) - return return_code.value + + locations = [] + for location in response.locationData: + locations.append(PartLocation(location)) + return locations except SherlockGetPartLocationError as e: LOG.error(str(e)) raise e diff --git a/src/ansys/sherlock/core/sherlock.py b/src/ansys/sherlock/core/sherlock.py index abee974c..315682fb 100644 --- a/src/ansys/sherlock/core/sherlock.py +++ b/src/ansys/sherlock/core/sherlock.py @@ -18,7 +18,6 @@ def __init__(self, channel): """Initialize Sherlock gRPC connection object.""" self.common = Common(channel) self.model = Model(channel) - self.lifecycle = Lifecycle(channel) self.project = Project(channel) self.lifecycle = Lifecycle(channel) self.layer = Layer(channel) diff --git a/src/ansys/sherlock/core/stackup.py b/src/ansys/sherlock/core/stackup.py index 1a6a38ac..91679eba 100644 --- a/src/ansys/sherlock/core/stackup.py +++ b/src/ansys/sherlock/core/stackup.py @@ -26,6 +26,7 @@ SherlockUpdateLaminateLayerError, ) from ansys.sherlock.core.grpc_stub import GrpcStub +from ansys.sherlock.core.types.stackup_types import StackupProperties class Stackup(GrpcStub): @@ -796,8 +797,8 @@ def get_stackup_props(self, project, cca_name): Returns ------- - list - The stackup properties of the CCA in the project. + StackupProperties + Object containing the properties of the stackup. Example ------- @@ -834,7 +835,7 @@ def get_stackup_props(self, project, cca_name): if response.returnCode.value == -1: raise SherlockGetLayerCountError(response.returnCode.message) - return response + return StackupProperties(response) except SherlockGetStackupPropsError as e: LOG.error(str(e)) raise e diff --git a/src/ansys/sherlock/core/types/analysis_types.py b/src/ansys/sherlock/core/types/analysis_types.py index ba36ebee..638ebd94 100644 --- a/src/ansys/sherlock/core/types/analysis_types.py +++ b/src/ansys/sherlock/core/types/analysis_types.py @@ -11,7 +11,7 @@ class ElementOrder: - """Values for Element Order.""" + """Constants for Element Order.""" LINEAR = analysis_service.ElementOrder.Linear QUADRATIC = analysis_service.ElementOrder.Quadratic @@ -19,14 +19,14 @@ class ElementOrder: class ModelSource: - """Values for Model Source.""" + """Constants for Model Source.""" GENERATED = analysis_service.ModelSource.GENERATED STRAIN_MAP = analysis_service.ModelSource.STRAIN_MAP class RunAnalysisRequestAnalysisType: - """Values for type of analysis in the Run Analysis request.""" + """Constants for type of analysis in the Run Analysis request.""" __analysis_type = analysis_service.RunAnalysisRequest.Analysis.AnalysisType NATURAL_FREQ = __analysis_type.NaturalFreq @@ -45,14 +45,14 @@ class RunAnalysisRequestAnalysisType: class RunStrainMapAnalysisRequestAnalysisType: - """Values for type of analysis in the Run Strain Map Analysis request.""" + """Constants for type of analysis in the Run Strain Map Analysis request.""" __analysis_type = analysis_service.RunStrainMapAnalysisRequest.StrainMapAnalysis.AnalysisType RANDOM_VIBE = __analysis_type.RandomVibe class UpdatePcbModelingPropsRequestAnalysisType: - """Values for type of analysis in the Update PCB Modeling Properties Analysis request.""" + """Constants for type of analysis in the Update PCB Modeling Properties Analysis request.""" __analysis_type = analysis_service.UpdatePcbModelingPropsRequest.Analysis.AnalysisType HARMONIC_VIBE = __analysis_type.HarmonicVibe @@ -64,7 +64,7 @@ class UpdatePcbModelingPropsRequestAnalysisType: class UpdatePcbModelingPropsRequestPcbMaterialModel: - """Values for PCB Material Model in the Update PCB Modeling Properties Analysis request.""" + """Constants for PCB Material Model in the Update PCB Modeling Properties Analysis request.""" __material_model = analysis_service.UpdatePcbModelingPropsRequest.Analysis.PcbMaterialModel UNIFORM = __material_model.Uniform @@ -74,7 +74,7 @@ class UpdatePcbModelingPropsRequestPcbMaterialModel: class UpdatePcbModelingPropsRequestPcbModelType: - """Values for PCB Model Type in the Update PCB Modeling Properties Analysis request.""" + """Constants for PCB Model Type in the Update PCB Modeling Properties Analysis request.""" __model_type = analysis_service.UpdatePcbModelingPropsRequest.Analysis.PcbModelType BONDED = __model_type.Bonded diff --git a/src/ansys/sherlock/core/types/common_types.py b/src/ansys/sherlock/core/types/common_types.py index 28ef2924..9bab0f51 100644 --- a/src/ansys/sherlock/core/types/common_types.py +++ b/src/ansys/sherlock/core/types/common_types.py @@ -9,7 +9,7 @@ class ListUnitsRequestUnitType: - """Values for Unit Type in the List Units request.""" + """Constants for Unit Type in the List Units request.""" ACCEL_DENSITY = SherlockCommonService_pb2.ListUnitsRequest.UnitType.ACCEL_DENSITY ACCELERATION = SherlockCommonService_pb2.ListUnitsRequest.UnitType.ACCELERATION diff --git a/src/ansys/sherlock/core/types/parts_types.py b/src/ansys/sherlock/core/types/parts_types.py index 85296f83..5db38c67 100644 --- a/src/ansys/sherlock/core/types/parts_types.py +++ b/src/ansys/sherlock/core/types/parts_types.py @@ -9,15 +9,36 @@ class UpdatesPartsListRequestMatchingMode: - """Values for Matching Mode in the Update Parts List request.""" + """Constants for Matching Mode in the Update Parts List request.""" BOTH = SherlockPartsService_pb2.UpdatePartsListRequest.MatchingMode.Both PART = SherlockPartsService_pb2.UpdatePartsListRequest.MatchingMode.Part class UpdatesPartsListRequestDuplicationMode: - """Values for Duplication Mode in the Update Parts List request.""" + """Constants for Duplication Mode in the Update Parts List request.""" FIRST = SherlockPartsService_pb2.UpdatePartsListRequest.DuplicationMode.First ERROR = SherlockPartsService_pb2.UpdatePartsListRequest.DuplicationMode.Error IGNORE = SherlockPartsService_pb2.UpdatePartsListRequest.DuplicationMode.Ignore + + +class PartLocation: + """Part Location property values.""" + + def __init__(self, location): + """Initialize members from the location.""" + self.x = location.x + """x coordinate""" + self.y = location.y + """y coordinate""" + self.rotation = location.rotation + """rotation (in degrees)""" + self.location_units = location.locationUnits + """units for location coordinates""" + self.board_side = location.boardSide + """board side - ``"TOP"`` or ``"BOTTOM"`` """ + self.mirrored = location.mirrored + """mirrored - ``True`` or ``False`` """ + self.ref_des = location.refDes + """reference designator""" diff --git a/src/ansys/sherlock/core/types/stackup_types.py b/src/ansys/sherlock/core/types/stackup_types.py new file mode 100644 index 00000000..0cac19f6 --- /dev/null +++ b/src/ansys/sherlock/core/types/stackup_types.py @@ -0,0 +1,18 @@ +# © 2023 ANSYS, Inc. All rights reserved. + +"""Module containing types for the Stackup Service.""" + + +class StackupProperties: + """Stackup property values.""" + + def __init__(self, properties): + """Initialize members from the properties.""" + self.board_dimension = properties.boardDimension + self.board_thickness = properties.boardThickness + self.density = properties.density + self.conductor_layers_cnt = properties.conductorLayersCnt + self.ctexy = properties.ctExy + self.ctez = properties.ctEz + self.exy = properties.exy + self.ez = properties.ez diff --git a/tests/test_parts.py b/tests/test_parts.py index b4125781..a218c6fa 100644 --- a/tests/test_parts.py +++ b/tests/test_parts.py @@ -539,26 +539,45 @@ def helper_test_get_part_location(parts): if parts._is_connection_up(): try: - result = parts.get_part_location( + parts.get_part_location( "Tutorial Project", - "Main Board", + "Invalid CCA", "C1", "in", ) - assert result == 0 + pytest.fail("No exception raised when using an invalid parameter") except Exception as e: - pytest.fail(e.message) + assert type(e) == SherlockGetPartLocationError try: - parts.get_part_location( + locations = parts.get_part_location( "Tutorial Project", - "Invalid CCA", - "C1", + "Main Board", + "C1, C3", "in", ) - pytest.fail("No exception raised when using an invalid parameter") - except Exception as e: - assert type(e) == SherlockGetPartLocationError + + assert len(locations) == 2, "Incorrect number of locations" + location_c1 = locations[0] + assert location_c1.ref_des == "C1", "Incorrect refDes" + assert location_c1.x == -2.7, "Incorrect X coordinate for C1" + assert location_c1.y == -1.65, "Incorrect Y coordinate for C1" + assert location_c1.rotation == 0, "Incorrect rotation for C1" + assert location_c1.location_units == "in", "Incorrect location units for C1" + assert location_c1.board_side == "TOP", "Incorrect board side for C1" + assert location_c1.mirrored is False, "Incorrect mirrored for C1" + + location_c3 = locations[1] + assert location_c3.ref_des == "C3", "Incorrect refDes" + assert location_c3.x == -2.4, "Incorrect X coordinate for C3" + assert location_c3.y == -1.9, "Incorrect Y coordinate for C3" + assert location_c3.rotation == 180, "Incorrect rotation for C3" + assert location_c3.location_units == "in", "Incorrect location units for C3" + assert location_c3.board_side == "TOP", "Incorrect board side for C3" + assert location_c3.mirrored is False, "Incorrect mirrored for C3" + + except SherlockGetPartLocationError as e: + pytest.fail(e.message) try: parts.get_part_location( diff --git a/tests/test_stackup.py b/tests/test_stackup.py index bb8a8156..32c7b87b 100644 --- a/tests/test_stackup.py +++ b/tests/test_stackup.py @@ -614,7 +614,14 @@ def helper_test_get_stackup_props(stackup): "Tutorial Project", "Main Board", ) - assert stackup_properties.conductorLayersCnt == "6" + assert stackup_properties.board_dimension == "190.32 x 114.31 mm [7.4928 x 4.5003 in]" + assert stackup_properties.board_thickness == "2.091 mm [82.3 mil]" + assert stackup_properties.density == "2.0340 g/cc" + assert stackup_properties.conductor_layers_cnt == "6" + assert stackup_properties.ctexy == "18.348 ppm/C" + assert stackup_properties.ctez == "64.217 ppm/C" + assert stackup_properties.exy == "27,182 MPa" + assert stackup_properties.ez == "6,943 MPa" except SherlockGetLayerCountError as e: pytest.fail(str(e)) From 7b7a036fcf0fb35d9386bb31feeb135afdef29f2 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:12:18 +0200 Subject: [PATCH 06/27] fix: wrong statement regarding Python versions (#137) --- doc/source/getting_started/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/getting_started/installation.rst b/doc/source/getting_started/installation.rst index 3edd6075..feeb943f 100644 --- a/doc/source/getting_started/installation.rst +++ b/doc/source/getting_started/installation.rst @@ -4,7 +4,7 @@ Install packages ================ -The ``ansys-sherlock-core`` package supports Python 3.7 through Python 3.10 on Windows. +The ``ansys-sherlock-core`` package supports Python 3.8 through Python 3.11 on Windows. To use PySherlock, you must download and install both the ``ansys-api-sherlock`` and ``ansys-sherlock-core`` packages. By using ``pip``, ``ansys-api-sherlock`` is From 289bebc6d4b5c1b0c0dfee0155726dfb6a8c9ed8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:58:13 -0500 Subject: [PATCH 07/27] MAINT: Bump ansys-api-sherlock from 0.1.15 to 0.1.16 (#138) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 852a3be4..043b8814 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-sherlock==0.1.15", + "ansys-api-sherlock==0.1.16", "grpcio>=1.17", "importlib-metadata>=4.0,<5; python_version<='3.8'", "protobuf~=3.20", From 6e813e22bcfefa28d44a5e6a67b572b57fe1ebf9 Mon Sep 17 00:00:00 2001 From: ansys-eermovic <136727891+ansys-eermovic@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:05:37 -0400 Subject: [PATCH 08/27] Add Solder Fatigue APIs (#139) --- src/ansys/sherlock/core/analysis.py | 179 +++++++++++++++++++++++++++- src/ansys/sherlock/core/common.py | 23 ++++ src/ansys/sherlock/core/errors.py | 12 ++ tests/test_analysis.py | 142 +++++++++++++++++++++- tests/test_common.py | 12 ++ tests/test_stackup.py | 12 +- 6 files changed, 372 insertions(+), 8 deletions(-) diff --git a/src/ansys/sherlock/core/analysis.py b/src/ansys/sherlock/core/analysis.py index 651e75f8..38735dea 100644 --- a/src/ansys/sherlock/core/analysis.py +++ b/src/ansys/sherlock/core/analysis.py @@ -18,6 +18,7 @@ SherlockUpdateNaturalFrequencyPropsError, SherlockUpdatePcbModelingPropsError, SherlockUpdateRandomVibePropsError, + SherlockUpdateSolderFatiguePropsError, ) from ansys.sherlock.core.grpc_stub import GrpcStub @@ -46,13 +47,17 @@ def __init__(self, channel): "naturalFreqMinUnits": "natural_freq_min_units", "naturalFreqMax": "natural_freq_max", "naturalFreqMaxUnits": "natural_freq_max_units", + "partTemp": "part_temp", + "partTempUnits": "part_temp_units", "partValidationEnabled": "part_validation_enabled", "performNFFreqRangeCheck": "perform_nf_freq_range_check", "randomVibeDamping": "random_vibe_damping", "requireMaterialAssignmentEnabled": "require_material_assignment_enabled", "reuseModalAnalysis": "reuse_modal_analysis", "shockResultCount": "shock_result_count", + "solderMaterial": "solder_material", "strainMapNaturalFreqs": "strain_map_natural_freqs", + "usePartTempRiseMin": "use_part_temp_rise_min", } def _translate_field_names(self, names_list): @@ -634,7 +639,7 @@ def update_mechanical_shock_props( if "cca_name" not in mechanical_shock_props.keys(): raise SherlockUpdateMechanicalShockPropsError( - message=f"CCA name is invalid for mechanical shock properties {i}." + message=f"CCA name is missing for mechanical shock properties {i}." ) cca_name = mechanical_shock_props["cca_name"] @@ -725,6 +730,178 @@ def update_mechanical_shock_props( LOG.error(str(e)) raise e + def get_solder_fatigue_input_fields(self): + """Get solder fatigue property fields based on the user configuration. + + Returns + ------- + list + List of solder fatigue property fields based on the user configuration. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> sherlock.project.import_odb_archive( + "ODB++ Tutorial.tgz", + True, + True, + True, + True, + project="Test", + cca_name="Card", + ) + >>> sherlock.analysis.get_solder_fatigue_input_fields() + """ + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + message = SherlockAnalysisService_pb2.GetSolderFatigueInputFieldsRequest() + response = self.stub.getSolderFatigueInputFields(message) + + fields = self._translate_field_names(response.fieldName) + LOG.info(fields) + + return fields + + def update_solder_fatigue_props( + self, + project, + solder_fatigue_properties, + ): + """Update properties for a solder fatigue analysis. + + Parameters + ---------- + project : str + Name of the Sherlock project. + solder_fatigue_properties : list + List of mechanical shock properties for a CCA consisting of these properties: + + - cca_name : str + Name of the CCA. + - solder_material: str + Solder material. The default is ``None``. + - part_temp : float + Part temperature. The default is ``None``. + - part_temp_units: str + Part temperature units. The default is ``None``. + - use_part_temp_rise_min: bool + whether to apply min temp rise. The default is ``None``. + - part_validation_enabled: bool + Whether to enable part validation. The default is ``None``. + + Returns + ------- + int + Status code of the response. 0 for success. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> sherlock.project.import_odb_archive( + "ODB++ Tutorial.tgz", + True, + True, + True, + True, + project="Test", + cca_name="Card", + ) + >>> sherlock.analysis.update_solder_fatigue_props( + "Test", + [{ + 'cca_name': 'Card', + 'solder_material': '63SN37PB', + 'part_temp': 70, + 'part_temp_units': 'F', + 'use_part_temp_rise_min': True, + 'part_validation_enabled': True + }, + ] + ) + + """ + try: + if project == "": + raise SherlockUpdateSolderFatiguePropsError(message="Project name is invalid.") + + if not isinstance(solder_fatigue_properties, list): + raise SherlockUpdateSolderFatiguePropsError( + message="Solder fatigue properties argument is invalid." + ) + + if len(solder_fatigue_properties) == 0: + raise SherlockUpdateSolderFatiguePropsError( + message="One or more solder fatigue properties are required." + ) + + request = SherlockAnalysisService_pb2.UpdateSolderFatiguePropsRequest(project=project) + + for i, solder_fatigue_props in enumerate(solder_fatigue_properties): + if not isinstance(solder_fatigue_props, dict): + raise SherlockUpdateSolderFatiguePropsError( + f"Solder fatigue props argument is invalid " + f"for solder fatigue properties {i}." + ) + + if "cca_name" not in solder_fatigue_props.keys(): + raise SherlockUpdateSolderFatiguePropsError( + message=f"CCA name is missing for solder fatigue properties {i}." + ) + + cca_name = solder_fatigue_props["cca_name"] + if cca_name == "": + raise SherlockUpdateSolderFatiguePropsError( + message=f"CCA name is invalid for solder fatigue properties {i}." + ) + + solder_material = solder_fatigue_props.get("solder_material", None) + part_temp = solder_fatigue_props.get("part_temp", None) + part_temp_units = solder_fatigue_props.get("part_temp_units", None) + use_part_temp_rise_min = solder_fatigue_props.get("use_part_temp_rise_min", None) + part_validation_enabled = solder_fatigue_props.get("part_validation_enabled", None) + + props_request = request.solderFatigueProperties.add() + props_request.ccaName = cca_name + + if solder_material is not None: + props_request.solderMaterial = solder_material + + if part_temp is not None: + props_request.partTemp = part_temp + + if part_temp_units is not None: + props_request.partTempUnits = part_temp_units + + if use_part_temp_rise_min is not None: + props_request.partTempRiseMinEnabled = use_part_temp_rise_min + + if part_validation_enabled is not None: + props_request.partValidationEnabled = part_validation_enabled + + except SherlockUpdateSolderFatiguePropsError as e: + LOG.error(str(e)) + raise e + + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + response = self.stub.updateSolderFatigueProps(request) + + try: + if response.value == -1: + raise SherlockUpdateSolderFatiguePropsError(response.message) + else: + LOG.info(response.message) + return response.value + except SherlockUpdateSolderFatiguePropsError as e: + LOG.error(str(e)) + raise e + def get_random_vibe_input_fields(self, model_source=None): """Get random vibe property fields based on the user configuration. diff --git a/src/ansys/sherlock/core/common.py b/src/ansys/sherlock/core/common.py index 7da9c493..7536b86b 100644 --- a/src/ansys/sherlock/core/common.py +++ b/src/ansys/sherlock/core/common.py @@ -113,3 +113,26 @@ def list_units(self, unitType): raise e return response.units + + def list_solder_materials(self): + """List valid solders. + + Returns + ------- + list + List of valid solder names. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> sherlock.common.list_solder_materials() + """ + if not self._is_connection_up(): + LOG.error("Not connected to a gRPC service.") + return + + request = SherlockCommonService_pb2.GetSoldersRequest() + response = self.stub.getSolders(request) + + return response.solderName diff --git a/src/ansys/sherlock/core/errors.py b/src/ansys/sherlock/core/errors.py index 222486ac..84966ea7 100644 --- a/src/ansys/sherlock/core/errors.py +++ b/src/ansys/sherlock/core/errors.py @@ -820,3 +820,15 @@ def __init__(self, message): def __str__(self): """Format error message.""" return f"Update mechanical shock properties error: {self.message}" + + +class SherlockUpdateSolderFatiguePropsError(Exception): + """Contains the error raised when properties for solder fatigue analysis cannot be updated.""" + + def __init__(self, message): + """Initialize error message.""" + self.message = message + + def __str__(self): + """Format error message.""" + return f"Update solder fatigue properties error: {self.message}" diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 47f962c9..34afc3a0 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -18,6 +18,7 @@ SherlockUpdateNaturalFrequencyPropsError, SherlockUpdatePcbModelingPropsError, SherlockUpdateRandomVibePropsError, + SherlockUpdateSolderFatiguePropsError, ) from ansys.sherlock.core.types.analysis_types import ( ElementOrder, @@ -40,10 +41,12 @@ def test_all(): helper_test_run_strain_map_analysis(analysis) helper_test_get_harmonic_vibe_input_fields(analysis) helper_test_get_mechanical_shock_input_fields(analysis) + helper_test_get_solder_fatigue_input_fields(analysis) helper_test_get_random_vibe_input_fields(analysis) helper_test_translate_field_names(analysis) helper_test_update_harmonic_vibe_props(analysis) helper_test_update_mechanical_shock_props(analysis) + helper_test_update_solder_fatigue_props(analysis) helper_test_update_random_vibe_props(analysis) helper_test_get_natural_frequency_input_fields(analysis) helper_test_update_natural_frequency_props(analysis) @@ -469,6 +472,16 @@ def helper_test_get_mechanical_shock_input_fields(analysis): assert "natural_freq_max_units" in fields +def helper_test_get_solder_fatigue_input_fields(analysis): + if analysis._is_connection_up(): + fields = analysis.get_solder_fatigue_input_fields() + assert "solder_material" in fields + assert "part_temp" in fields + assert "part_temp_units" in fields + assert "use_part_temp_rise_min" in fields + assert "part_validation_enabled" in fields + + def helper_test_get_random_vibe_input_fields(analysis): if analysis._is_connection_up(): fields = analysis.get_random_vibe_input_fields() @@ -780,7 +793,7 @@ def helper_test_update_mechanical_shock_props(analysis): except SherlockUpdateMechanicalShockPropsError as e: assert ( str(e) == "Update mechanical shock properties error: " - "CCA name is invalid for mechanical shock properties 0." + "CCA name is missing for mechanical shock properties 0." ) try: @@ -867,6 +880,133 @@ def helper_test_update_mechanical_shock_props(analysis): pytest.fail(str(e)) +def helper_test_update_solder_fatigue_props(analysis): + try: + analysis.update_solder_fatigue_props( + "", + [ + { + "cca_name": "Card", + "solder_material": "63SN37PB", + "part_temp": 70, + "part_temp_units": "F", + "use_part_temp_rise_min": True, + "part_validation_enabled": True, + }, + ], + ) + assert False + except SherlockUpdateSolderFatiguePropsError as e: + assert str(e) == "Update solder fatigue properties error: Project name is invalid." + + try: + analysis.update_solder_fatigue_props("Test", "INVALID_TYPE") + assert False + except SherlockUpdateSolderFatiguePropsError as e: + assert ( + str(e) == "Update solder fatigue properties error: " + "Solder fatigue properties argument is invalid." + ) + + try: + analysis.update_solder_fatigue_props("Test", []) + assert False + except SherlockUpdateSolderFatiguePropsError as e: + assert ( + str(e) == "Update solder fatigue properties error: " + "One or more solder fatigue properties are required." + ) + + try: + analysis.update_solder_fatigue_props("Test", ["INVALID"]) + assert False + except SherlockUpdateSolderFatiguePropsError as e: + assert ( + str(e) == "Update solder fatigue properties error: " + "Solder fatigue props argument is invalid for solder fatigue properties 0." + ) + + try: + analysis.update_solder_fatigue_props( + "Tutorial Project", + [ + { + "solder_material": "63SN37PB", + "part_temp": 70, + "part_temp_units": "F", + "use_part_temp_rise_min": True, + "part_validation_enabled": True, + }, + ], + ) + assert False + except SherlockUpdateSolderFatiguePropsError as e: + assert ( + str(e) == "Update solder fatigue properties error: " + "CCA name is missing for solder fatigue properties 0." + ) + + try: + analysis.update_solder_fatigue_props( + "Tutorial Project", + [ + { + "cca_name": "", + "solder_material": "63SN37PB", + "part_temp": 70, + "part_temp_units": "F", + "use_part_temp_rise_min": True, + "part_validation_enabled": True, + }, + ], + ) + assert False + except SherlockUpdateSolderFatiguePropsError as e: + assert ( + str(e) == "Update solder fatigue properties error: " + "CCA name is invalid for solder fatigue properties 0." + ) + + if not analysis._is_connection_up(): + return + + try: + analysis.update_solder_fatigue_props( + "Tutorial Project", + [ + { + "cca_name": "Main Board", + "solder_material": "63SN37PB", + "part_temp": 70, + "part_temp_units": "INVALID", + "use_part_temp_rise_min": True, + "part_validation_enabled": True, + }, + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockUpdateSolderFatiguePropsError + + try: + result = analysis.update_solder_fatigue_props( + "Tutorial Project", + [ + { + "cca_name": "Main Board", + "solder_material": "63SN37PB", + "part_temp": 70, + "part_temp_units": "F", + "use_part_temp_rise_min": True, + "part_validation_enabled": True, + }, + ], + ) + assert result == 0 + except SherlockUpdateSolderFatiguePropsError as e: + pytest.fail(str(e)) + + def helper_test_update_random_vibe_props(analysis): try: analysis.update_random_vibe_props( diff --git a/tests/test_common.py b/tests/test_common.py index 98b681d1..88fa2693 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -14,6 +14,7 @@ def test_all(): channel = grpc.insecure_channel(channel_param) common = Common(channel) helper_test_list_units(common) + helper_test_get_solders(common) def helper_test_list_units(common): @@ -33,5 +34,16 @@ def helper_test_list_units(common): pytest.fail(str(e)) +def helper_test_get_solders(common): + """Test get_solders API""" + + if common._is_connection_up(): + try: + solders = common.list_solder_materials() + assert len(solders) != 0 + except SherlockCommonServiceError as e: + pytest.fail(str(e)) + + if __name__ == "__main__": test_all() diff --git a/tests/test_stackup.py b/tests/test_stackup.py index 32c7b87b..c4f3f87e 100644 --- a/tests/test_stackup.py +++ b/tests/test_stackup.py @@ -268,7 +268,7 @@ def helper_test_update_conductor_layer(stackup): "Main Board", "3", "POWER", - "ALUMINA", + "ALUMINUM", # thickness=0.5, # thickness_unit="oz", conductor_percent="94.2", @@ -616,12 +616,12 @@ def helper_test_get_stackup_props(stackup): ) assert stackup_properties.board_dimension == "190.32 x 114.31 mm [7.4928 x 4.5003 in]" assert stackup_properties.board_thickness == "2.091 mm [82.3 mil]" - assert stackup_properties.density == "2.0340 g/cc" + assert stackup_properties.density == "2.0264 g/cc" assert stackup_properties.conductor_layers_cnt == "6" - assert stackup_properties.ctexy == "18.348 ppm/C" - assert stackup_properties.ctez == "64.217 ppm/C" - assert stackup_properties.exy == "27,182 MPa" - assert stackup_properties.ez == "6,943 MPa" + assert stackup_properties.ctexy == "18.618 ppm/C" + assert stackup_properties.ctez == "64.355 ppm/C" + assert stackup_properties.exy == "25,420 MPa" + assert stackup_properties.ez == "6,939 MPa" except SherlockGetLayerCountError as e: pytest.fail(str(e)) From c3855be97ee226924b9df9140aad6906995f66fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 19:22:17 +0000 Subject: [PATCH 09/27] MAINT: Bump pytest from 7.3.2 to 7.4.0 (#136) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 043b8814..fe92f410 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ tests = [ "grpcio==1.55.0", "protobuf==3.20.3", - "pytest==7.3.2", + "pytest==7.4.0", "pytest-cov==4.1.0", ] doc = [ From 68bc0febca216e825bf290975c83aca27e0cb8fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 19:35:59 +0000 Subject: [PATCH 10/27] MAINT: Bump grpcio from 1.55.0 to 1.56.0 (#135) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fe92f410..52e2b5ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ [project.optional-dependencies] tests = [ - "grpcio==1.55.0", + "grpcio==1.56.0", "protobuf==3.20.3", "pytest==7.4.0", "pytest-cov==4.1.0", From dcf8bd91c19367a0b59750f88b18c5955342af62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 10:29:22 -0500 Subject: [PATCH 11/27] MAINT: Bump grpcio from 1.56.0 to 1.56.2 (#142) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 52e2b5ca..a7972405 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ [project.optional-dependencies] tests = [ - "grpcio==1.56.0", + "grpcio==1.56.2", "protobuf==3.20.3", "pytest==7.4.0", "pytest-cov==4.1.0", From 39269a56e9323529bd002272176e67215b4fe648 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:43:19 +0000 Subject: [PATCH 12/27] MAINT: Bump ansys-api-sherlock from 0.1.16 to 0.1.17 (#141) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a7972405..7442179f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-sherlock==0.1.16", + "ansys-api-sherlock==0.1.17", "grpcio>=1.17", "importlib-metadata>=4.0,<5; python_version<='3.8'", "protobuf~=3.20", From cc047861358fd0aa1ea4bb4a4c2056adcbbcc20b Mon Sep 17 00:00:00 2001 From: ansys-eermovic <136727891+ansys-eermovic@users.noreply.github.com> Date: Tue, 25 Jul 2023 10:15:22 -0400 Subject: [PATCH 13/27] Add add_cca API (#140) --- src/ansys/sherlock/core/errors.py | 12 +++ src/ansys/sherlock/core/project.py | 131 +++++++++++++++++++++++++++++ tests/test_project.py | 128 ++++++++++++++++++++++++++++ tests/test_stackup.py | 4 +- 4 files changed, 273 insertions(+), 2 deletions(-) diff --git a/src/ansys/sherlock/core/errors.py b/src/ansys/sherlock/core/errors.py index 84966ea7..773987b3 100644 --- a/src/ansys/sherlock/core/errors.py +++ b/src/ansys/sherlock/core/errors.py @@ -832,3 +832,15 @@ def __init__(self, message): def __str__(self): """Format error message.""" return f"Update solder fatigue properties error: {self.message}" + + +class SherlockAddCCAError(Exception): + """Contains the error raised when CCA cannot be added.""" + + def __init__(self, message): + """Initialize error message.""" + self.message = message + + def __str__(self): + """Format error message.""" + return f"Add CCA error: {self.message}" diff --git a/src/ansys/sherlock/core/project.py b/src/ansys/sherlock/core/project.py index da995043..32bf6662 100644 --- a/src/ansys/sherlock/core/project.py +++ b/src/ansys/sherlock/core/project.py @@ -12,6 +12,7 @@ from ansys.sherlock.core import LOG from ansys.sherlock.core.errors import ( + SherlockAddCCAError, SherlockAddStrainMapsError, SherlockDeleteProjectError, SherlockGenerateProjectReportError, @@ -357,6 +358,136 @@ def list_ccas(self, project, cca_names=None): return response.ccas + def add_cca(self, project, cca_properties): + """Add one or more CCAs to a project. + + Parameters + ---------- + project : str + Name of the Sherlock project. + cca_properties : list + List of CCAs to be added consisting of these properties: + + - cca_name : str + Name of the CCA. + - description : str + Description of the CCA. The default is ``None``. + - default_solder_type: str + The default solder type. The default is ``None``. + - default_stencil_thickness: float + The default stencil thickness. The default is ``None``. + - default_stencil_thickness_units: str + Units for default stencil thickness. The default is ``None``. + - default_part_temp_rise: float + Default part temp rise. The default is ``None``. + - default_part_temp_rise_units: str + Units for default part temp rise. The default is ``None``. + Options are ``"C"``, ``"F"``, and ``"K"``. + - guess_part_properties_enabled: bool + Whether to enable guess part properties. The default is ``None``. + + Returns + ------- + int + Status code of the response. 0 for success. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> sherlock.project.import_odb_archive( + "ODB++ Tutorial.tgz", + True, + True, + True, + True, + project="Test", + cca_name="Card", + ) + >>> sherlock.project.add_cca( + "Test", + [{ + 'cca_name': 'Card 2', + 'description': 'Second CCA', + 'default_solder_type': 'SAC305', + 'default_stencil_thickness': 10, + 'default_stencil_thickness_units': 'mm', + 'default_part_temp_rise': 20, + 'default_part_temp_rise_units': 'C', + 'guess_part_properties_enabled': False, + }, + ] + ) + + """ + try: + if project == "": + raise SherlockAddCCAError(message="Project name is invalid.") + + if not isinstance(cca_properties, list): + raise SherlockAddCCAError(message="CCA properties argument is invalid.") + + if len(cca_properties) == 0: + raise SherlockAddCCAError(message="One or more CCAs are required.") + + request = SherlockProjectService_pb2.AddCcaRequest(project=project) + + for i, cca in enumerate(cca_properties): + if not isinstance(cca, dict): + raise SherlockAddCCAError(message=f"CCA properties are invalid for CCA {i}.") + + if "cca_name" not in cca.keys(): + raise SherlockAddCCAError(message=f"CCA name is missing for CCA {i}.") + + cca_request = request.CCAs.add() + cca_request.ccaName = cca["cca_name"] + + if cca_request.ccaName == "": + raise SherlockAddCCAError(message=f"CCA name is invalid for CCA {i}.") + + if "description" in cca.keys(): + cca_request.description = cca["description"] + + if "default_solder_type" in cca.keys(): + cca_request.defaultSolderType = cca["default_solder_type"] + + if "default_stencil_thickness" in cca.keys(): + cca_request.defaultStencilThickness = cca["default_stencil_thickness"] + + if "default_stencil_thickness_units" in cca.keys(): + cca_request.defaultStencilThicknessUnits = cca[ + "default_stencil_thickness_units" + ] + + if "default_part_temp_rise" in cca.keys(): + cca_request.defaultPartTempRise = cca["default_part_temp_rise"] + + if "default_part_temp_rise_units" in cca.keys(): + cca_request.defaultPartTempRiseUnits = cca["default_part_temp_rise_units"] + + if "guess_part_properties_enabled" in cca.keys(): + cca_request.guessPartPropertiesEnabled = cca["guess_part_properties_enabled"] + + except SherlockAddCCAError as e: + LOG.error(str(e)) + raise e + + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + response = self.stub.addCCA(request) + + try: + if response.value == -1: + raise SherlockAddCCAError(response.message) + else: + LOG.info(response.message) + return response.value + except SherlockAddCCAError as e: + LOG.error(str(e)) + raise e + def add_strain_maps(self, project, strain_maps): """Add a CSV file with strain maps to the CCAs. diff --git a/tests/test_project.py b/tests/test_project.py index 32391275..8a9dbd10 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -2,11 +2,13 @@ import os import platform +import uuid import grpc import pytest from ansys.sherlock.core.errors import ( + SherlockAddCCAError, SherlockAddStrainMapsError, SherlockDeleteProjectError, SherlockGenerateProjectReportError, @@ -30,6 +32,7 @@ def test_all(): helper_test_import_ipc2581_archive(project) helper_test_generate_project_report(project) helper_test_list_ccas(project) + helper_test_add_cca(project) helper_test_list_strain_maps(project) @@ -170,6 +173,131 @@ def helper_test_list_ccas(project): pytest.fail(str(e.str_itr())) +def helper_test_add_cca(project): + """Test add_cca API""" + + try: + project.add_cca( + "", + [ + { + "cca_name": "Card 2", + "description": "Second CCA", + "default_solder_type": "SAC305", + "default_stencil_thickness": 10, + "default_stencil_thickness_units": "mm", + "default_part_temp_rise": 20, + "default_part_temp_rise_units": "C", + "guess_part_properties_enabled": False, + }, + ], + ) + assert False + except SherlockAddCCAError as e: + assert str(e) == "Add CCA error: Project name is invalid." + + try: + project.add_cca("Test", "") + assert False + except SherlockAddCCAError as e: + assert str(e) == "Add CCA error: CCA properties argument is invalid." + + try: + project.add_cca("Test", []) + assert False + except SherlockAddCCAError as e: + assert str(e) == "Add CCA error: One or more CCAs are required." + + try: + project.add_cca("Test", [""]) + assert False + except SherlockAddCCAError as e: + assert str(e) == "Add CCA error: CCA properties are invalid for CCA 0." + + try: + project.add_cca( + "Test", + [ + { + "description": "Second CCA", + "default_solder_type": "SAC305", + "default_stencil_thickness": 10, + "default_stencil_thickness_units": "mm", + "default_part_temp_rise": 20, + "default_part_temp_rise_units": "C", + "guess_part_properties_enabled": False, + }, + ], + ) + assert False + except SherlockAddCCAError as e: + assert str(e) == "Add CCA error: CCA name is missing for CCA 0." + + try: + project.add_cca( + "Test", + [ + { + "cca_name": "", + "description": "Second CCA", + "default_solder_type": "SAC305", + "default_stencil_thickness": 10, + "default_stencil_thickness_units": "mm", + "default_part_temp_rise": 20, + "default_part_temp_rise_units": "C", + "guess_part_properties_enabled": False, + }, + ], + ) + assert False + except SherlockAddCCAError as e: + assert str(e) == "Add CCA error: CCA name is invalid for CCA 0." + + if not project._is_connection_up(): + return + + try: + project.add_cca( + "Test", + [ + { + "cca_name": "Name", + "description": "Second CCA", + "default_solder_type": "SAC305", + "default_stencil_thickness": 10, + "default_stencil_thickness_units": "INVALID", + "default_part_temp_rise": 20, + "default_part_temp_rise_units": "C", + "guess_part_properties_enabled": False, + }, + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockAddCCAError + + cca_name = "Test Card " + str(uuid.uuid4()) + try: + result = project.add_cca( + "Tutorial Project", + [ + { + "cca_name": cca_name, + "description": "Second CCA", + "default_solder_type": "SAC305", + "default_stencil_thickness": 10, + "default_stencil_thickness_units": "mm", + "default_part_temp_rise": 20, + "default_part_temp_rise_units": "C", + "guess_part_properties_enabled": False, + }, + ], + ) + assert result == 0 + except SherlockAddCCAError as e: + pytest.fail(str(e)) + + def helper_test_add_strain_maps(project): """Test add_strain_maps API""" diff --git a/tests/test_stackup.py b/tests/test_stackup.py index c4f3f87e..9f653c77 100644 --- a/tests/test_stackup.py +++ b/tests/test_stackup.py @@ -487,7 +487,7 @@ def helper_test_list_conductor_layers(stackup): try: layer_properties_per_board = stackup.list_conductor_layers("Tutorial Project") - assert len(layer_properties_per_board) == 1 + assert len(layer_properties_per_board) >= 1 layer_properties_of_board = layer_properties_per_board[0] assert layer_properties_of_board.ccaName == "Main Board" layer_properties_per_layer = layer_properties_of_board.conductorLayerProps @@ -516,7 +516,7 @@ def helper_test_list_laminate_layers(stackup): try: layer_properties_per_board = stackup.list_laminate_layers("Tutorial Project") - assert len(layer_properties_per_board) == 1 + assert len(layer_properties_per_board) >= 1 layer_properties_of_board = layer_properties_per_board[0] assert layer_properties_of_board.ccaName == "Main Board" layer_properties_per_layer = layer_properties_of_board.laminateProps From 903b5797a5c5f1c7bdb5d9e411cca64a5627a234 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:15:12 -0500 Subject: [PATCH 14/27] MAINT: Bump ansys-api-sherlock from 0.1.17 to 0.1.18 (#144) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7442179f..a8e8e7ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-sherlock==0.1.17", + "ansys-api-sherlock==0.1.18", "grpcio>=1.17", "importlib-metadata>=4.0,<5; python_version<='3.8'", "protobuf~=3.20", From 170b28f2005d7254aafe3e87161dca3fe464de9b Mon Sep 17 00:00:00 2001 From: ansys-khanson <122566160+ansys-khanson@users.noreply.github.com> Date: Wed, 26 Jul 2023 12:33:06 -0400 Subject: [PATCH 15/27] Added PR template (#145) --- PULL_REQUEST_TEMPLATE.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..3102a059 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +Checklist: +- [] Run unit tests and make sure they all pass +- [] Check and fix style errors + - pre-commit command line check + - Problems tab in PyCharm +- [] Bench test new/modified APIs by using and modifying the code in the example for the API method +- [] Add new methods to rst file for updated API script(\doc\source\api) +- [] Generate documentation +- [] Verify the HTML. It gets generated at: \doc\build\html. + - Open index.html + - Click on "API Reference" at the top. + - Verify HTML for API changes. +- [] Check that test code coverage is at least 80% when Sherlock is running and can be connected to using gRPC From 26ddf5bab61ab2ba8bd32ead15af864a46c36f3a Mon Sep 17 00:00:00 2001 From: ansys-eermovic <136727891+ansys-eermovic@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:46:54 -0400 Subject: [PATCH 16/27] Add API to add potting region (#143) --- doc/source/api/analysis.rst | 4 +- doc/source/api/common.rst | 1 + doc/source/api/index.rst | 2 + doc/source/api/layer.rst | 1 + doc/source/api/layer_types.rst | 17 ++ doc/source/api/project.rst | 1 + src/ansys/sherlock/core/errors.py | 12 + src/ansys/sherlock/core/layer.py | 199 ++++++++++++++- src/ansys/sherlock/core/types/layer_types.py | 71 ++++++ tests/test_layer.py | 245 ++++++++++++++++++- 10 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 doc/source/api/layer_types.rst create mode 100644 src/ansys/sherlock/core/types/layer_types.py diff --git a/doc/source/api/analysis.rst b/doc/source/api/analysis.rst index 91eadefd..c411ff9b 100644 --- a/doc/source/api/analysis.rst +++ b/doc/source/api/analysis.rst @@ -13,10 +13,12 @@ The ``analysis`` module contains all analysis capabilities. ansys.sherlock.core.analysis.Analysis.get_harmonic_vibe_input_fields ansys.sherlock.core.analysis.Analysis.get_mechanical_shock_input_fields ansys.sherlock.core.analysis.Analysis.get_random_vibe_input_fields + ansys.sherlock.core.analysis.Analysis.get_solder_fatigue_input_fields ansys.sherlock.core.analysis.Analysis.run_analysis ansys.sherlock.core.analysis.Analysis.run_strain_map_analysis ansys.sherlock.core.analysis.Analysis.update_harmonic_vibe_props ansys.sherlock.core.analysis.Analysis.update_mechanical_shock_props ansys.sherlock.core.analysis.Analysis.update_natural_frequency_props ansys.sherlock.core.analysis.Analysis.update_pcb_modeling_props - ansys.sherlock.core.analysis.Analysis.update_random_vibe_props \ No newline at end of file + ansys.sherlock.core.analysis.Analysis.update_random_vibe_props + ansys.sherlock.core.analysis.Analysis.update_solder_fatigue_props \ No newline at end of file diff --git a/doc/source/api/common.rst b/doc/source/api/common.rst index 66e22cd4..3ffcc6ce 100644 --- a/doc/source/api/common.rst +++ b/doc/source/api/common.rst @@ -13,4 +13,5 @@ The ``common`` module contains all common capabilities. ansys.sherlock.core.common.Common.check ansys.sherlock.core.common.Common.exit ansys.sherlock.core.common.Common.is_sherlock_client_loading + ansys.sherlock.core.common.Common.list_solder_materials ansys.sherlock.core.common.Common.list_units diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index e99acab2..9dfe6ce9 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -17,6 +17,7 @@ Use the search feature or click links to view API documentation. lifecycle model parts + layer_types parts_types project stackup @@ -30,6 +31,7 @@ Use the search feature or click links to view API documentation. ansys.sherlock.core.lifecycle ansys.sherlock.core.model ansys.sherlock.core.parts + ansys.sherlock.core.types.layer_types ansys.sherlock.core.types.parts_types ansys.sherlock.core.project ansys.sherlock.core.stackup diff --git a/doc/source/api/layer.rst b/doc/source/api/layer.rst index 01a011fa..5b97ef7d 100644 --- a/doc/source/api/layer.rst +++ b/doc/source/api/layer.rst @@ -12,3 +12,4 @@ The ``layer`` module contains all layer management capabilities. :toctree: _autosummary ansys.sherlock.core.layer.Layer.update_mount_points_by_file + ansys.sherlock.core.layer.Layer.add_potting_region diff --git a/doc/source/api/layer_types.rst b/doc/source/api/layer_types.rst new file mode 100644 index 00000000..6d1cc5f5 --- /dev/null +++ b/doc/source/api/layer_types.rst @@ -0,0 +1,17 @@ +.. _ref_layer_types: + +Layer Types +=========== + +Classes used for the Layer API. + +.. currentmodule:: ansys.sherlock.core.types.layer_types + +.. autosummary:: + :toctree: _autosummary + + PolygonalShape + RectangularShape + SlotShape + CircularShape + PCBShape \ No newline at end of file diff --git a/doc/source/api/project.rst b/doc/source/api/project.rst index df770783..ae2dff5c 100644 --- a/doc/source/api/project.rst +++ b/doc/source/api/project.rst @@ -10,6 +10,7 @@ The ``project`` module contains all project management capabilities. .. autosummary:: :toctree: _autosummary + ansys.sherlock.core.project.Project.add_cca ansys.sherlock.core.project.Project.add_strain_maps ansys.sherlock.core.project.Project.delete_project ansys.sherlock.core.project.Project.generate_project_report diff --git a/src/ansys/sherlock/core/errors.py b/src/ansys/sherlock/core/errors.py index 773987b3..0284236b 100644 --- a/src/ansys/sherlock/core/errors.py +++ b/src/ansys/sherlock/core/errors.py @@ -844,3 +844,15 @@ def __init__(self, message): def __str__(self): """Format error message.""" return f"Add CCA error: {self.message}" + + +class SherlockAddPottingRegionError(Exception): + """Contains the error raised when a potting region cannot be added.""" + + def __init__(self, message): + """Initialize error message.""" + self.message = message + + def __str__(self): + """Format error message.""" + return f"Add potting region error: {self.message}" diff --git a/src/ansys/sherlock/core/layer.py b/src/ansys/sherlock/core/layer.py index 85f6257b..58c6ca9c 100644 --- a/src/ansys/sherlock/core/layer.py +++ b/src/ansys/sherlock/core/layer.py @@ -1,6 +1,13 @@ # © 2023 ANSYS, Inc. All rights reserved """Module containing all layer management capabilities.""" +from ansys.sherlock.core.types.layer_types import ( + CircularShape, + PCBShape, + PolygonalShape, + RectangularShape, + SlotShape, +) try: import SherlockLayerService_pb2 @@ -10,7 +17,10 @@ from ansys.api.sherlock.v0 import SherlockLayerService_pb2_grpc from ansys.sherlock.core import LOG -from ansys.sherlock.core.errors import SherlockUpdateMountPointsByFileError +from ansys.sherlock.core.errors import ( + SherlockAddPottingRegionError, + SherlockUpdateMountPointsByFileError, +) from ansys.sherlock.core.grpc_stub import GrpcStub @@ -22,6 +32,193 @@ def __init__(self, channel): super().__init__(channel) self.stub = SherlockLayerService_pb2_grpc.SherlockLayerServiceStub(channel) + def add_potting_region( + self, + project, + potting_regions, + ): + """Add one or more potting regions to a given project. + + Parameters + ---------- + project : str + Name of the Sherlock project. + potting_regions : list + List of potting region properties consisting of these properties: + + - cca_name: str + Name of the CCA. + - potting_id: str + Potting ID. The default is ``None``. + - side: str + The side to add the potting region to. The default is ``None``. + Options are ``"TOP"``, ``"BOTTOM"``, and ``"BOT"``. + - material: str + The potting material. The default is ``None``. + - potting_units: str + The potting region units. The default is ``None``. + - thickness: float + The potting thickness. The default is ``None``. + - standoff: float + The potting standoff. The default is ``None``. + - shape: PolygonalShape|RectangularShape|SlotShape|CircularShape|PCBShape + The shape of the potting region. + + + Returns + ------- + int + Status code of the response. 0 for success. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> from ansys.sherlock.core.types.layer_types import PolygonalShape + >>> sherlock = launch_sherlock() + >>> sherlock.project.import_odb_archive( + "ODB++ Tutorial.tgz", + True, + True, + True, + True, + project="Test", + cca_name="Card", + ) + >>> polygonal_shape = PolygonalShape(points=[ + (0, 0), + (0, 6.35), + (9.77, 0) + ], rotation=87.8) + >>> sherlock.layer.add_potting_region( + "Test", + [{ + 'cca_name': 'Card', + 'potting_id': 'Test Region', + 'side': 'TOP', + 'material': 'epoxyencapsulant', + 'potting_units': 'in', + 'thickness': 0.1, + 'standoff': 0.2, + 'shape': polygonal_shape + }, + ] + ) + + """ + try: + if project == "": + raise SherlockAddPottingRegionError(message="Project name is invalid.") + + if not isinstance(potting_regions, list): + raise SherlockAddPottingRegionError(message="Potting regions argument is invalid.") + + if len(potting_regions) == 0: + raise SherlockAddPottingRegionError( + message="One or more potting regions are required." + ) + + request = SherlockLayerService_pb2.AddPottingRegionRequest(project=project) + + for i, potting_region in enumerate(potting_regions): + if not isinstance(potting_region, dict): + raise SherlockAddPottingRegionError( + message=f"Potting region argument is invalid for potting region {i}." + ) + + if "cca_name" not in potting_region.keys(): + raise SherlockAddPottingRegionError( + message=f"CCA name is missing for potting region {i}." + ) + + region_request = request.pottingRegions.add() + region_request.ccaName = potting_region["cca_name"] + + if region_request.ccaName == "": + raise SherlockAddPottingRegionError( + message=f"CCA name is invalid for potting region {i}." + ) + + if "potting_id" in potting_region.keys(): + region_request.pottingID = potting_region["potting_id"] + if "side" in potting_region.keys(): + region_request.pottingSide = potting_region["side"] + if "material" in potting_region.keys(): + region_request.pottingMaterial = potting_region["material"] + if "potting_units" in potting_region.keys(): + region_request.pottingUnits = potting_region["potting_units"] + if "thickness" in potting_region.keys(): + region_request.pottingThickness = potting_region["thickness"] + if "standoff" in potting_region.keys(): + region_request.pottingStandoff = potting_region["standoff"] + if "shape" not in potting_region.keys(): + raise SherlockAddPottingRegionError( + message=f"Shape missing for potting region {i}." + ) + + shape = potting_region["shape"] + + if isinstance(shape, PolygonalShape): + if not isinstance(shape.points, list): + raise SherlockAddPottingRegionError( + message=f"Invalid points argument for potting region {i}." + ) + + for j, point in enumerate(shape.points): + point_message = region_request.polygonalShape.points.add() + + if not isinstance(point, tuple) or len(point) != 2: + raise SherlockAddPottingRegionError( + message=f"Point {j} invalid for potting region {i}." + ) + point_message.x = point[0] + point_message.y = point[1] + region_request.polygonalShape.rotation = shape.rotation + elif isinstance(shape, RectangularShape): + region_request.rectangularShape.length = shape.length + region_request.rectangularShape.width = shape.width + region_request.rectangularShape.centerX = shape.center_x + region_request.rectangularShape.centerY = shape.center_y + region_request.rectangularShape.rotation = shape.rotation + elif isinstance(shape, SlotShape): + region_request.slotShape.length = shape.length + region_request.slotShape.width = shape.width + region_request.slotShape.nodeCount = shape.node_count + region_request.slotShape.centerX = shape.center_x + region_request.slotShape.centerY = shape.center_y + region_request.slotShape.rotation = shape.rotation + elif isinstance(shape, CircularShape): + region_request.circularShape.diameter = shape.diameter + region_request.circularShape.nodeCount = shape.node_count + region_request.circularShape.centerX = shape.center_x + region_request.circularShape.centerY = shape.center_y + region_request.circularShape.rotation = shape.rotation + elif isinstance(shape, PCBShape): + region_request.pCBShape.CopyFrom(SherlockLayerService_pb2.PCBShape()) + else: + raise SherlockAddPottingRegionError( + message=f"Shape invalid for potting region {i}." + ) + + except SherlockAddPottingRegionError as e: + LOG.error(str(e)) + raise e + + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + response = self.stub.addPottingRegion(request) + + try: + if response.value == -1: + raise SherlockAddPottingRegionError(response.message) + else: + LOG.info(response.message) + return response.value + except SherlockAddPottingRegionError as e: + LOG.error(str(e)) + raise e + def update_mount_points_by_file( self, project, diff --git a/src/ansys/sherlock/core/types/layer_types.py b/src/ansys/sherlock/core/types/layer_types.py new file mode 100644 index 00000000..8468f441 --- /dev/null +++ b/src/ansys/sherlock/core/types/layer_types.py @@ -0,0 +1,71 @@ +# © 2023 ANSYS, Inc. All rights reserved. + +"""Module containing types for the Layer Service.""" + + +class PolygonalShape: + """Contains the properties for a polygonal shape.""" + + def __init__(self, points, rotation): + """Initialize the shape properties.""" + self.points = points + """points (length two tuples of the form (x, y)) : list[tuple[float, float]]""" + self.rotation = rotation + """rotation (in degrees) : float""" + + +class RectangularShape: + """Contains the properties for a rectangular shape.""" + + def __init__(self, length, width, center_x, center_y, rotation): + """Initialize the shape properties.""" + self.length = length + """length : float""" + self.width = width + """width : float""" + self.center_x = center_x + """x coordinate of center : float""" + self.center_y = center_y + """y coordinate of center : float""" + self.rotation = rotation + """rotation (in degrees) : float""" + + +class SlotShape: + """Contains the properties for a slot shape.""" + + def __init__(self, length, width, node_count, center_x, center_y, rotation): + """Initialize the shape properties.""" + self.length = length + """length : float""" + self.width = width + """width : float""" + self.node_count = node_count + """node count : int""" + self.center_x = center_x + """x coordinate of center : float""" + self.center_y = center_y + """y coordinate of center : float""" + self.rotation = rotation + """rotation (in degrees) : float""" + + +class CircularShape: + """Contains the properties for a circular shape.""" + + def __init__(self, diameter, node_count, center_x, center_y, rotation): + """Initialize the shape properties.""" + self.diameter = diameter + """diameter : float""" + self.node_count = node_count + """node count : int""" + self.center_x = center_x + """x coordinate of center : float""" + self.center_y = center_y + """y coordinate of center : float""" + self.rotation = rotation + """rotation (in degrees) : float""" + + +class PCBShape: + """Contains the properties for a PCB shape.""" diff --git a/tests/test_layer.py b/tests/test_layer.py index d65d7885..3cf191a3 100644 --- a/tests/test_layer.py +++ b/tests/test_layer.py @@ -1,10 +1,15 @@ # © 2023 ANSYS, Inc. All rights reserved +import uuid import grpc import pytest -from ansys.sherlock.core.errors import SherlockUpdateMountPointsByFileError +from ansys.sherlock.core.errors import ( + SherlockAddPottingRegionError, + SherlockUpdateMountPointsByFileError, +) from ansys.sherlock.core.layer import Layer +from ansys.sherlock.core.types.layer_types import PCBShape, PolygonalShape def test_all(): @@ -14,6 +19,244 @@ def test_all(): layer = Layer(channel) helper_test_update_mount_points_by_file(layer) + helper_test_add_potting_region(layer) + + +def helper_test_add_potting_region(layer): + """Test add_potting_region API.""" + try: + shape = PCBShape() + layer.add_potting_region( + "", + [ + { + "cca_name": "Main Board", + "potting_id": "Test Region", + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": shape, + }, + ], + ) + pytest.fail("No exception thrown when using invalid project name.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: Project name is invalid." + + try: + layer.add_potting_region("Test", "") + pytest.fail("No exception thrown when using invalid potting regions list.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: Potting regions argument is invalid." + + try: + layer.add_potting_region("Test", []) + pytest.fail("No exception thrown when using empty potting regions list.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: One or more potting regions are required." + + try: + layer.add_potting_region("Test", ["test"]) + pytest.fail("No exception thrown when using invalid element in potting regions list.") + except SherlockAddPottingRegionError as e: + assert ( + str(e) == "Add potting region error: " + "Potting region argument is invalid for potting region 0." + ) + + try: + shape = PCBShape() + layer.add_potting_region( + "Test", + [ + { + "potting_id": "Test Region", + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": shape, + }, + ], + ) + pytest.fail("No exception thrown when cca name missing.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: CCA name is missing for potting region 0." + + try: + shape = PCBShape() + layer.add_potting_region( + "Test", + [ + { + "cca_name": "", + "potting_id": "Test Region", + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": shape, + }, + ], + ) + pytest.fail("No exception thrown when cca name is invalid.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: CCA name is invalid for potting region 0." + + try: + layer.add_potting_region( + "Test", + [ + { + "cca_name": "Test Card", + "potting_id": "Test Region", + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + }, + ], + ) + pytest.fail("No exception thrown when shape missing.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: Shape missing for potting region 0." + + try: + layer.add_potting_region( + "Test", + [ + { + "cca_name": "Test Card", + "potting_id": "Test Region", + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": "INVALID", + }, + ], + ) + pytest.fail("No exception thrown when shape is invalid.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: Shape invalid for potting region 0." + + try: + shape = PolygonalShape(points="INVALID", rotation=123.4) + layer.add_potting_region( + "Test", + [ + { + "cca_name": "Test Card", + "potting_id": "Test Region", + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": shape, + }, + ], + ) + pytest.fail("No exception thrown when polygonal points list is invalid.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: Invalid points argument for potting region 0." + + try: + invalid_point = "INVALID" + shape = PolygonalShape(points=[(1, 2), (4.4, 5.5), invalid_point], rotation=123.4) + layer.add_potting_region( + "Test", + [ + { + "cca_name": "Test Card", + "potting_id": "Test Region", + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": shape, + }, + ], + ) + pytest.fail("No exception thrown when polygonal points list element is incorrect type.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: Point 2 invalid for potting region 0." + + try: + invalid_point = (4.4, 5.5, 10) + shape = PolygonalShape(points=[(1, 2), invalid_point, (1, 6)], rotation=123.4) + layer.add_potting_region( + "Test", + [ + { + "cca_name": "Test Card", + "potting_id": "Test Region", + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": shape, + }, + ], + ) + pytest.fail("No exception thrown when polygonal points list element is incorrect length.") + except SherlockAddPottingRegionError as e: + assert str(e) == "Add potting region error: Point 1 invalid for potting region 0." + + if not layer._is_connection_up(): + return + + try: + potting_id = f"Test Region {uuid.uuid4()}" + shape = PolygonalShape(points=[(1, 2), (4.4, 5.5), (1, 6)], rotation=123.4) + layer.add_potting_region( + "Tutorial Project", + [ + { + "cca_name": "Main Board", + "potting_id": potting_id, + "side": "INVALID", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": shape, + }, + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockAddPottingRegionError + + try: + potting_id = f"Test Region {uuid.uuid4()}" + shape = PolygonalShape(points=[(1, 2), (4.4, 5.5), (1, 6)], rotation=123.4) + result = layer.add_potting_region( + "Tutorial Project", + [ + { + "cca_name": "Main Board", + "potting_id": potting_id, + "side": "TOP", + "material": "epoxyencapsulant", + "potting_units": "in", + "thickness": 0.1, + "standoff": 0.2, + "shape": shape, + }, + ], + ) + assert result == 0 + except SherlockAddPottingRegionError as e: + pytest.fail(str(e)) def helper_test_update_mount_points_by_file(layer): From 23445131ca8684c4947d94ceda096ea443f22936 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 10:47:22 -0500 Subject: [PATCH 17/27] MAINT: Bump ansys-api-sherlock from 0.1.18 to 0.1.19 (#148) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a8e8e7ee..4ee427ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-sherlock==0.1.18", + "ansys-api-sherlock==0.1.19", "grpcio>=1.17", "importlib-metadata>=4.0,<5; python_version<='3.8'", "protobuf~=3.20", From 7128b16732d22f8bfb216d70e498e30efb5c06f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 16:01:43 +0000 Subject: [PATCH 18/27] MAINT: Bump ansys-sphinx-theme from 0.9.9 to 0.10.0 (#147) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jeff Moody <110494049+ansys-jmoody@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ee427ea..09022b76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ tests = [ "pytest-cov==4.1.0", ] doc = [ - "ansys-sphinx-theme==0.9.9", + "ansys-sphinx-theme==0.10.0", "numpydoc==1.5.0", "Sphinx==6.2.1", # BLOCKED BY sphinx-design - Cannot upgrade to Sphinx 7 for now "sphinx-copybutton==0.5.2", From 35fd9e42c8dce5d1b89cf9c77e86d4ac31eb0de8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:20:02 -0500 Subject: [PATCH 19/27] MAINT: Bump sphinx-design from 0.4.1 to 0.5.0 (#149) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 09022b76..848109fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ doc = [ "numpydoc==1.5.0", "Sphinx==6.2.1", # BLOCKED BY sphinx-design - Cannot upgrade to Sphinx 7 for now "sphinx-copybutton==0.5.2", - "sphinx_design==0.4.1", + "sphinx_design==0.5.0", "sphinx-gallery==0.13.0", ] From 56437823290214a834955e9eb78f53ef7e569507 Mon Sep 17 00:00:00 2001 From: ansys-khanson <122566160+ansys-khanson@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:21:26 -0400 Subject: [PATCH 20/27] Keith/add project (#146) Co-authored-by: Jeff Moody --- doc/source/api/project.rst | 3 +- src/ansys/sherlock/core/errors.py | 12 ++++++++ src/ansys/sherlock/core/project.py | 45 ++++++++++++++++++++++++++++++ tests/test_project.py | 40 ++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/doc/source/api/project.rst b/doc/source/api/project.rst index ae2dff5c..da85171f 100644 --- a/doc/source/api/project.rst +++ b/doc/source/api/project.rst @@ -17,4 +17,5 @@ The ``project`` module contains all project management capabilities. ansys.sherlock.core.project.Project.import_odb_archive ansys.sherlock.core.project.Project.import_ipc2581_archive ansys.sherlock.core.project.Project.list_ccas - ansys.sherlock.core.project.Project.list_strain_maps \ No newline at end of file + ansys.sherlock.core.project.Project.list_strain_maps + ansys.sherlock.core.project.Project.add_project \ No newline at end of file diff --git a/src/ansys/sherlock/core/errors.py b/src/ansys/sherlock/core/errors.py index 0284236b..3f202ce6 100644 --- a/src/ansys/sherlock/core/errors.py +++ b/src/ansys/sherlock/core/errors.py @@ -834,6 +834,18 @@ def __str__(self): return f"Update solder fatigue properties error: {self.message}" +class SherlockAddProjectError(Exception): + """Contains the error raised when Project cannot be added.""" + + def __init__(self, message): + """Initialize error message.""" + self.message = message + + def __str__(self): + """Format error message.""" + return f"Add project error: {self.message}" + + class SherlockAddCCAError(Exception): """Contains the error raised when CCA cannot be added.""" diff --git a/src/ansys/sherlock/core/project.py b/src/ansys/sherlock/core/project.py index 32bf6662..c58767c8 100644 --- a/src/ansys/sherlock/core/project.py +++ b/src/ansys/sherlock/core/project.py @@ -13,6 +13,7 @@ from ansys.sherlock.core import LOG from ansys.sherlock.core.errors import ( SherlockAddCCAError, + SherlockAddProjectError, SherlockAddStrainMapsError, SherlockDeleteProjectError, SherlockGenerateProjectReportError, @@ -675,3 +676,47 @@ def list_strain_maps(self, project, cca_names=None): raise e return response.ccaStrainMaps + + def add_project(self, project_name: str, project_category: str, project_description: str): + """Add a sherlock project to sherlock. + + Parameters + ---------- + project_name: str + Name of the Sherlock project. + project_category: str + Category of the Sherlock project + project_description: str + Description of the Sherlock project + + Returns + ------- + int + 0 for success otherwise error + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> code = sherlock.project.add_project( + "project name example", + "project category example", + "project description example") + """ + if project_name is None or project_name == "": + raise SherlockAddProjectError("Project name cannot be blank") + + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + request = SherlockProjectService_pb2.AddProjectRequest( + project=project_name, category=project_category, description=project_description + ) + + return_code = self.stub.addProject(request) + + if return_code.value == -1: + raise SherlockAddProjectError(return_code.message) + + return return_code.value diff --git a/tests/test_project.py b/tests/test_project.py index 8a9dbd10..c8d0ccae 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -2,6 +2,7 @@ import os import platform +import time import uuid import grpc @@ -9,6 +10,7 @@ from ansys.sherlock.core.errors import ( SherlockAddCCAError, + SherlockAddProjectError, SherlockAddStrainMapsError, SherlockDeleteProjectError, SherlockGenerateProjectReportError, @@ -19,6 +21,8 @@ ) from ansys.sherlock.core.project import Project +PROJECT_ADD_NAME = "Delete This After Add" + def test_all(): """Test all project APIs""" @@ -34,6 +38,11 @@ def test_all(): helper_test_list_ccas(project) helper_test_add_cca(project) helper_test_list_strain_maps(project) + project_name = None + try: + project_name = helper_test_add_project(project) + finally: + clean_up_after_add(project, project_name) def helper_test_delete_project(project): @@ -563,5 +572,36 @@ def helper_test_list_strain_maps(project): pytest.fail(str(e.str_itr())) +def helper_test_add_project(project): + """Test add_project API""" + + try: + project.add_project("", "", "") + pytest.fail("No exception raised when using an invalid parameter") + except SherlockAddProjectError as e: + assert str(e) == "Add project error: Project name cannot be blank" + + if project._is_connection_up(): + try: + project.add_project("Tutorial Project", "", "") + pytest.fail("No exception raised when creating a duplicate project") + except Exception as e: + assert type(e) == SherlockAddProjectError + + try: + return_code = project.add_project(PROJECT_ADD_NAME, "", "") + assert return_code == 0 + # Fix issue where api does not finish before returning + time.sleep(1) + return PROJECT_ADD_NAME + except SherlockAddProjectError as e: + pytest.fail(str(e)) + + +def clean_up_after_add(project, project_name): + if project_name is not None: + project.delete_project(project_name) + + if __name__ == "__main__": test_all() From 441dee1d9bec3244fbf1a94b4b872bc636de32aa Mon Sep 17 00:00:00 2001 From: ansys-eermovic <136727891+ansys-eermovic@users.noreply.github.com> Date: Fri, 4 Aug 2023 11:47:36 -0400 Subject: [PATCH 21/27] Add partmodeling API (#150) --- doc/source/api/analysis.rst | 1 + pyproject.toml | 2 +- src/ansys/sherlock/core/analysis.py | 135 +++++++++++++++++++++++++++- src/ansys/sherlock/core/errors.py | 12 +++ src/ansys/sherlock/core/project.py | 7 +- tests/test_analysis.py | 126 ++++++++++++++++++++++++++ 6 files changed, 278 insertions(+), 5 deletions(-) diff --git a/doc/source/api/analysis.rst b/doc/source/api/analysis.rst index c411ff9b..8fead9db 100644 --- a/doc/source/api/analysis.rst +++ b/doc/source/api/analysis.rst @@ -19,6 +19,7 @@ The ``analysis`` module contains all analysis capabilities. ansys.sherlock.core.analysis.Analysis.update_harmonic_vibe_props ansys.sherlock.core.analysis.Analysis.update_mechanical_shock_props ansys.sherlock.core.analysis.Analysis.update_natural_frequency_props + ansys.sherlock.core.analysis.Analysis.update_part_modeling_props ansys.sherlock.core.analysis.Analysis.update_pcb_modeling_props ansys.sherlock.core.analysis.Analysis.update_random_vibe_props ansys.sherlock.core.analysis.Analysis.update_solder_fatigue_props \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 848109fd..71cd019f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ - "ansys-api-sherlock==0.1.19", + "ansys-api-sherlock==0.1.20", "grpcio>=1.17", "importlib-metadata>=4.0,<5; python_version<='3.8'", "protobuf~=3.20", diff --git a/src/ansys/sherlock/core/analysis.py b/src/ansys/sherlock/core/analysis.py index 38735dea..f1a80391 100644 --- a/src/ansys/sherlock/core/analysis.py +++ b/src/ansys/sherlock/core/analysis.py @@ -16,6 +16,7 @@ SherlockUpdateHarmonicVibePropsError, SherlockUpdateMechanicalShockPropsError, SherlockUpdateNaturalFrequencyPropsError, + SherlockUpdatePartModelingPropsError, SherlockUpdatePcbModelingPropsError, SherlockUpdateRandomVibePropsError, SherlockUpdateSolderFatiguePropsError, @@ -1108,8 +1109,8 @@ def get_natural_frequency_input_fields(self): Examples -------- >>> from ansys.sherlock.core.launcher import launch_sherlock - >>> sherlock.project.import_odb_archive( >>> sherlock = launch_sherlock() + >>> sherlock.project.import_odb_archive( "ODB++ Tutorial.tgz", True, True, @@ -1291,7 +1292,7 @@ def run_strain_map_analysis( >>> from ansys.sherlock.core.launcher import launch_sherlock >>> sherlock = launch_sherlock() >>> analysis_request = SherlockAnalysisService_pb2.RunStrainMapAnalysisRequest - >>> analysis.run_strain_map_analysis( + >>> sherlock.analysis.run_strain_map_analysis( "AssemblyTutorial", "Main Board", [[ @@ -1516,3 +1517,133 @@ def update_pcb_modeling_props(self, project, cca_names, analyses): except SherlockUpdatePcbModelingPropsError as e: LOG.error(str(e)) raise e + + def update_part_modeling_props(self, project, part_modeling_props): + """Update part modeling properties for a given project's CCA. + + Parameters + ---------- + project : str + Name of the Sherlock project. + part_modeling_props : dict + Dict of part modeling properties for a CCA consisting of these properties: + + - cca_name : str + Name of the CCA. + - part_enabled : bool + Whether to enable part modeling. All other fields are ignored if disabled. + - part_min_size : float, optional + Minimum part size. + - part_min_size_units : str, optional + Minimum part size units. + - part_elem_order : str, optional + Part element order. + Options are ``"First Order (Linear)"``, ``"Second Order (Quadratic)"``, + or ``"Solid Shell"``. + - part_max_edge_length : float, optional + Part max edge length. + - part_max_edge_length_units : str, optional + Part max edge length units. + - part_max_vertical : float, optional + Part max vertical. + - part_max_vertical_units : str, optional + Part max vertical units. + - part_results_filtered : bool, optional + Whether to enable filtered part results. + + Returns + ------- + int + Status code of the response. 0 for success. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> sherlock.project.import_odb_archive( + "ODB++ Tutorial.tgz", + True, + True, + True, + True, + project="Test", + cca_name="Card", + ) + >>> sherlock.analysis.update_part_modeling_props( + "Test", + { + 'cca_name': 'Card', + 'part_enabled': True, + 'part_min_size': 1, + 'part_min_size_units': 'in', + 'part_elem_order': 'First Order (Linear)', + 'part_max_edge_length': 1, + 'part_max_edge_length_units': 'in', + 'part_max_vertical': 1, + 'part_max_vertical_units': 'in', + 'part_results_filtered': True + } + ) + + """ + try: + if project == "": + raise SherlockUpdatePartModelingPropsError(message="Project name is invalid.") + + if not isinstance(part_modeling_props, dict): + raise SherlockUpdatePartModelingPropsError( + message="Part modeling props argument is invalid." + ) + + if "cca_name" not in part_modeling_props.keys(): + raise SherlockUpdatePartModelingPropsError(message="CCA name is missing.") + if "part_enabled" not in part_modeling_props.keys(): + raise SherlockUpdatePartModelingPropsError(message="Part enabled is missing.") + + request = SherlockAnalysisService_pb2.UpdatePartModelingRequest(project=project) + request.ccaName = part_modeling_props["cca_name"] + + if request.ccaName == "": + raise SherlockUpdatePartModelingPropsError(message="CCA name is invalid.") + + request.partEnabled = part_modeling_props["part_enabled"] + + if request.partEnabled: + if "part_min_size" in part_modeling_props.keys(): + request.partMinSize = part_modeling_props["part_min_size"] + if "part_min_size_units" in part_modeling_props.keys(): + request.partMinSizeUnits = part_modeling_props["part_min_size_units"] + if "part_elem_order" in part_modeling_props.keys(): + request.partElemOrder = part_modeling_props["part_elem_order"] + if "part_max_edge_length" in part_modeling_props.keys(): + request.partMaxEdgeLength = part_modeling_props["part_max_edge_length"] + if "part_max_edge_length_units" in part_modeling_props.keys(): + request.partMaxEdgeLengthUnits = part_modeling_props[ + "part_max_edge_length_units" + ] + if "part_max_vertical" in part_modeling_props.keys(): + request.partMaxVertical = part_modeling_props["part_max_vertical"] + if "part_max_vertical_units" in part_modeling_props.keys(): + request.partMaxVerticalUnits = part_modeling_props["part_max_vertical_units"] + if "part_results_filtered" in part_modeling_props.keys(): + request.partResultsFiltered = part_modeling_props["part_results_filtered"] + + except SherlockUpdatePartModelingPropsError as e: + LOG.error(str(e)) + raise e + + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + response = self.stub.updatePartModelingProperties(request) + + try: + if response.value == -1: + raise SherlockUpdatePartModelingPropsError(response.message) + else: + LOG.info(response.message) + return response.value + except SherlockUpdatePartModelingPropsError as e: + LOG.error(str(e)) + raise e diff --git a/src/ansys/sherlock/core/errors.py b/src/ansys/sherlock/core/errors.py index 3f202ce6..96df0dc6 100644 --- a/src/ansys/sherlock/core/errors.py +++ b/src/ansys/sherlock/core/errors.py @@ -868,3 +868,15 @@ def __init__(self, message): def __str__(self): """Format error message.""" return f"Add potting region error: {self.message}" + + +class SherlockUpdatePartModelingPropsError(Exception): + """Contains the error raised when part modeling properties cannot be updated.""" + + def __init__(self, message): + """Initialize error message.""" + self.message = message + + def __str__(self): + """Format error message.""" + return f"Update part modeling props error: {self.message}" diff --git a/src/ansys/sherlock/core/project.py b/src/ansys/sherlock/core/project.py index c58767c8..39a2b2a1 100644 --- a/src/ansys/sherlock/core/project.py +++ b/src/ansys/sherlock/core/project.py @@ -524,7 +524,7 @@ def add_strain_maps(self, project, strain_maps): -------- >>> from ansys.sherlock.core.launcher import launch_sherlock >>> sherlock = launch_sherlock() - >>> project.add_strain_maps("Tutorial Project", + >>> sherlock.project.add_strain_maps("Tutorial Project", [("StrainMap.csv", "This is the strain map file for the project", 0, @@ -641,7 +641,10 @@ def list_strain_maps(self, project, cca_names=None): -------- >>> from ansys.sherlock.core.launcher import launch_sherlock >>> sherlock = launch_sherlock() - >>> strain_maps = project.list_strain_maps("AssemblyTutorial",["Main Board","Power Module"]) + >>> strain_maps = sherlock.project.list_strain_maps( + "AssemblyTutorial", + ["Main Board","Power Module"] + ) """ try: if project == "": diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 34afc3a0..58a2fadb 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -16,6 +16,7 @@ SherlockUpdateHarmonicVibePropsError, SherlockUpdateMechanicalShockPropsError, SherlockUpdateNaturalFrequencyPropsError, + SherlockUpdatePartModelingPropsError, SherlockUpdatePcbModelingPropsError, SherlockUpdateRandomVibePropsError, SherlockUpdateSolderFatiguePropsError, @@ -51,6 +52,7 @@ def test_all(): helper_test_get_natural_frequency_input_fields(analysis) helper_test_update_natural_frequency_props(analysis) helper_test_update_pcb_modeling_props(analysis) + helper_test_update_part_modeling_props(analysis) def helper_test_run_analysis(analysis): @@ -1359,5 +1361,129 @@ def helper_test_update_pcb_modeling_props(analysis): assert pytest.fail(e.message) +def helper_test_update_part_modeling_props(analysis): + try: + analysis.update_part_modeling_props( + "", + { + "cca_name": "Card", + "part_enabled": True, + "part_min_size": 1, + "part_min_size_units": "in", + "part_elem_order": "First Order (Linear)", + "part_max_edge_length": 1, + "part_max_edge_length_units": "in", + "part_max_vertical": 1, + "part_max_vertical_units": "in", + "part_results_filtered": True, + }, + ) + pytest.fail("No exception thrown when project is the empty string.") + except SherlockUpdatePartModelingPropsError as e: + assert str(e) == "Update part modeling props error: Project name is invalid." + + try: + analysis.update_part_modeling_props( + "Test", + { + "cca_name": "", + "part_enabled": True, + "part_min_size": 1, + "part_min_size_units": "in", + "part_elem_order": "First Order (Linear)", + "part_max_edge_length": 1, + "part_max_edge_length_units": "in", + "part_max_vertical": 1, + "part_max_vertical_units": "in", + "part_results_filtered": True, + }, + ) + pytest.fail("No exception thrown when cca name is the empty string.") + except SherlockUpdatePartModelingPropsError as e: + assert str(e) == "Update part modeling props error: CCA name is invalid." + + try: + analysis.update_part_modeling_props("Test", "INVALID") + pytest.fail("No exception thrown when part modeling props is incorrect type.") + except SherlockUpdatePartModelingPropsError as e: + assert ( + str(e) == "Update part modeling props error: " + "Part modeling props argument is invalid." + ) + + try: + analysis.update_part_modeling_props( + "Test", + { + "part_enabled": True, + "part_min_size": 1, + "part_min_size_units": "in", + "part_elem_order": "First Order (Linear)", + "part_max_edge_length": 1, + "part_max_edge_length_units": "in", + "part_max_vertical": 1, + "part_max_vertical_units": "in", + "part_results_filtered": True, + }, + ) + pytest.fail("No exception thrown when CCA name is missing.") + except SherlockUpdatePartModelingPropsError as e: + assert str(e) == "Update part modeling props error: CCA name is missing." + + try: + analysis.update_part_modeling_props( + "Test", + { + "cca_name": "Card", + "part_min_size": 1, + "part_min_size_units": "in", + "part_elem_order": "First Order (Linear)", + "part_max_edge_length": 1, + "part_max_edge_length_units": "in", + "part_max_vertical": 1, + "part_max_vertical_units": "in", + "part_results_filtered": True, + }, + ) + pytest.fail("No exception thrown when part enabled is missing.") + except SherlockUpdatePartModelingPropsError as e: + assert str(e) == "Update part modeling props error: Part enabled is missing." + + if not analysis._is_connection_up(): + return + + try: + analysis.update_part_modeling_props( + "Tutorial Project", + { + "cca_name": "Main Board", + "part_enabled": True, + "part_min_size": 1, + "part_min_size_units": "in", + "part_elem_order": "INVALID", + "part_max_edge_length": 1, + "part_max_edge_length_units": "in", + "part_max_vertical": 1, + "part_max_vertical_units": "in", + "part_results_filtered": True, + }, + ) + pytest.fail("No exception raised when using an invalid parameter.") + except Exception as e: + assert type(e) == SherlockUpdatePartModelingPropsError + + try: + result = analysis.update_part_modeling_props( + "Tutorial Project", + { + "cca_name": "Main Board", + "part_enabled": False, + }, + ) + assert result == 0 + except SherlockUpdatePartModelingPropsError as e: + pytest.fail(str(e)) + + if __name__ == "__main__": test_all() From 71c160121fda6bc5377a6c204acbb65226c5c4ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:24:43 -0500 Subject: [PATCH 22/27] MAINT: Bump ansys-sphinx-theme from 0.10.0 to 0.10.2 (#151) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 71cd019f..3547e64e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ tests = [ "pytest-cov==4.1.0", ] doc = [ - "ansys-sphinx-theme==0.10.0", + "ansys-sphinx-theme==0.10.2", "numpydoc==1.5.0", "Sphinx==6.2.1", # BLOCKED BY sphinx-design - Cannot upgrade to Sphinx 7 for now "sphinx-copybutton==0.5.2", From 8816d68e30f4eea9c771d356d4673b6aa09940f2 Mon Sep 17 00:00:00 2001 From: Jeff Moody <110494049+ansjmoody@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:59:22 -0500 Subject: [PATCH 23/27] test_layer: changed material for unit tests for add_potting_region when connected to Sherlock (#153) --- doc/source/api/launcher.rst | 2 ++ src/ansys/sherlock/core/analysis.py | 4 ++-- src/ansys/sherlock/core/launcher.py | 19 ++++++++++++------- tests/test_layer.py | 20 ++++++++++---------- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/doc/source/api/launcher.rst b/doc/source/api/launcher.rst index 7092ab9a..d4eb4c07 100644 --- a/doc/source/api/launcher.rst +++ b/doc/source/api/launcher.rst @@ -12,3 +12,5 @@ The ``launcher`` module launches the Sherlock gRPC server and a Sherlock client. :toctree: _autosummary ansys.sherlock.core.launcher.launch_sherlock + ansys.sherlock.core.launcher.connect_grpc_channel + diff --git a/src/ansys/sherlock/core/analysis.py b/src/ansys/sherlock/core/analysis.py index f1a80391..7b963587 100644 --- a/src/ansys/sherlock/core/analysis.py +++ b/src/ansys/sherlock/core/analysis.py @@ -1265,7 +1265,7 @@ def run_strain_map_analysis( strain_map_analyses : list List of analyses consisting of these properties: - - analysis_type : RunStrainMapAnalysisRequest.StrainMapAnalysis.AnalysisType + - analysis_type : RunStrainMapAnalysisRequestAnalysisType Type of analysis to run. - event_strain_maps : list List of the strain maps assigned to the desired life cycle events for @@ -1296,7 +1296,7 @@ def run_strain_map_analysis( "AssemblyTutorial", "Main Board", [[ - analysis_request.StrainMapAnalysis.AnalysisType.RandomVibe, + RunStrainMapAnalysisRequestAnalysisType.RANDOM_VIBE, [["Phase 1", "Random Vibe", "TOP", "MainBoardStrain - Top"], ["Phase 1", "Random Vibe", "BOTTOM", "MainBoardStrain - Bottom"], ["Phase 1", "Random Vibe", "TOP", "MemoryCard1Strain", "Memory Card 1"]], diff --git a/src/ansys/sherlock/core/launcher.py b/src/ansys/sherlock/core/launcher.py index 68d8dc10..4e90f994 100644 --- a/src/ansys/sherlock/core/launcher.py +++ b/src/ansys/sherlock/core/launcher.py @@ -42,13 +42,11 @@ def launch_sherlock( IP address to start gRPC on. The default is ``"127.0.0.1"``, which is the IP address for the local host. port : int, optional - Port number for the connection. If no port is specified, ``9090`` - is used. + Port number for the connection. single_project_path : str, optional Path to the Sherlock project if invoking Sherlock in the single-project mode. - The default is ``""``. sherlock_cmd_args : str, optional - Additional command arguments for launching Sherlock. The default is ``""``. + Additional command arguments for launching Sherlock. Returns ------- @@ -75,7 +73,7 @@ def launch_sherlock( return None try: - args = _get_sherlock_exe_path() + " " + "-grpcPort=" + str(port) + args = _get_sherlock_exe_path() + " -grpcPort=" + str(port) if single_project_path != "": args = f'{args} -singleProject "{single_project_path}"' if sherlock_cmd_args != "": @@ -108,9 +106,16 @@ def launch_sherlock( def connect_grpc_channel(port=SHERLOCK_DEFAULT_PORT): - """Create a gRPC connection to a specified port and return the ``sherlock``connection object. + """Create a gRPC connection to a specified port and return the ``Sherlock`` connection object. - The ``sherlock``connection object is used to invoke the APIs from their respective services. + The ``Sherlock`` connection object is used to invoke the APIs from their respective services. + This can be used to connect to the Sherlock instance that is already running with the specified + port. + + Parameters + ---------- + port : int, optional + Port number for the connection. Returns ------- diff --git a/tests/test_layer.py b/tests/test_layer.py index 3cf191a3..17c9698a 100644 --- a/tests/test_layer.py +++ b/tests/test_layer.py @@ -33,7 +33,7 @@ def helper_test_add_potting_region(layer): "cca_name": "Main Board", "potting_id": "Test Region", "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -74,7 +74,7 @@ def helper_test_add_potting_region(layer): { "potting_id": "Test Region", "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -95,7 +95,7 @@ def helper_test_add_potting_region(layer): "cca_name": "", "potting_id": "Test Region", "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -115,7 +115,7 @@ def helper_test_add_potting_region(layer): "cca_name": "Test Card", "potting_id": "Test Region", "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -134,7 +134,7 @@ def helper_test_add_potting_region(layer): "cca_name": "Test Card", "potting_id": "Test Region", "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -155,7 +155,7 @@ def helper_test_add_potting_region(layer): "cca_name": "Test Card", "potting_id": "Test Region", "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -177,7 +177,7 @@ def helper_test_add_potting_region(layer): "cca_name": "Test Card", "potting_id": "Test Region", "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -199,7 +199,7 @@ def helper_test_add_potting_region(layer): "cca_name": "Test Card", "potting_id": "Test Region", "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -224,7 +224,7 @@ def helper_test_add_potting_region(layer): "cca_name": "Main Board", "potting_id": potting_id, "side": "INVALID", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, @@ -246,7 +246,7 @@ def helper_test_add_potting_region(layer): "cca_name": "Main Board", "potting_id": potting_id, "side": "TOP", - "material": "epoxyencapsulant", + "material": "COPPER", "potting_units": "in", "thickness": 0.1, "standoff": 0.2, From b63e707fbde89e125d89e5a4a95757a9ac78ad78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Aug 2023 17:13:29 +0000 Subject: [PATCH 24/27] MAINT: Bump grpcio from 1.56.2 to 1.57.0 (#154) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3547e64e..6c6251fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ [project.optional-dependencies] tests = [ - "grpcio==1.56.2", + "grpcio==1.57.0", "protobuf==3.20.3", "pytest==7.4.0", "pytest-cov==4.1.0", From e9def22b3d79d68efbbcbcba47b57bd70a365bce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 10:30:29 -0500 Subject: [PATCH 25/27] MAINT: Bump ansys-sphinx-theme from 0.10.2 to 0.10.3 (#155) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6c6251fe..df208dec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ tests = [ "pytest-cov==4.1.0", ] doc = [ - "ansys-sphinx-theme==0.10.2", + "ansys-sphinx-theme==0.10.3", "numpydoc==1.5.0", "Sphinx==6.2.1", # BLOCKED BY sphinx-design - Cannot upgrade to Sphinx 7 for now "sphinx-copybutton==0.5.2", From 7e361037da332d802a73c53fdc31cec70f28f754 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 17:55:21 -0500 Subject: [PATCH 26/27] MAINT: Bump sphinx-gallery from 0.13.0 to 0.14.0 (#157) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index df208dec..555a2da8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ doc = [ "Sphinx==6.2.1", # BLOCKED BY sphinx-design - Cannot upgrade to Sphinx 7 for now "sphinx-copybutton==0.5.2", "sphinx_design==0.5.0", - "sphinx-gallery==0.13.0", + "sphinx-gallery==0.14.0", ] [project.urls] From e7346bebfba65267a3a5e60f7f6e949ec84e1e15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:26:10 -0500 Subject: [PATCH 27/27] MAINT: Bump ansys-sphinx-theme from 0.10.3 to 0.10.4 (#158) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 555a2da8..96387ff5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ tests = [ "pytest-cov==4.1.0", ] doc = [ - "ansys-sphinx-theme==0.10.3", + "ansys-sphinx-theme==0.10.4", "numpydoc==1.5.0", "Sphinx==6.2.1", # BLOCKED BY sphinx-design - Cannot upgrade to Sphinx 7 for now "sphinx-copybutton==0.5.2",