Skip to content

Commit d0a75fb

Browse files
SMoraisAnsysboltmapre-commit-ci[bot]pyansys-ci-bot
authored
FEAT: Project sheet (#6757)
Co-authored-by: Xiaoyang Ma <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent 122be69 commit d0a75fb

File tree

4 files changed

+196
-0
lines changed

4 files changed

+196
-0
lines changed

doc/changelog.d/6757.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Project sheet

src/ansys/aedt/core/modeler/cad/primitives.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5870,6 +5870,65 @@ def wrap_sheet(self, sheet, object, imprinted=False):
58705870
self.cleanup_objects()
58715871
return True
58725872

5873+
def project_sheet(self, sheet, object, thickness, draft_angle=0, angle_unit="deg", keep_originals=True):
5874+
"""Project sheet on an object.
5875+
5876+
If projection produces an unclassified operation it will be reverted.
5877+
5878+
Parameters
5879+
----------
5880+
sheet : str, int, or :class:`ansys.aedt.core.modeler.cad.object_3d.Object3d`
5881+
Sheet name, id, or sheet object.
5882+
object : list, str, int, or :class:`ansys.aedt.core.modeler.cad.object_3d.Object3d`
5883+
Object name, id, or solid object to be projected on.
5884+
thickness : float, str
5885+
Thickness of the projected sheet in model units.
5886+
draft_angle : float, str, optional
5887+
Draft angle for the projection. Default is ``0``.
5888+
angle_unit : str, optional
5889+
Angle unit. Default is ``deg``.
5890+
keep_originals : bool, optional
5891+
Whether to keep the original objects. Default is ``True``.
5892+
5893+
Returns
5894+
-------
5895+
bool
5896+
``True`` when successful, ``False`` when failed.
5897+
5898+
References
5899+
----------
5900+
>>> oEditor.ProjectSheet
5901+
"""
5902+
sheet = self.convert_to_selections(sheet, False)
5903+
object = self.convert_to_selections(object, False)
5904+
5905+
try:
5906+
unclassified = [i for i in self.unclassified_objects]
5907+
self.oeditor.ProjectSheet(
5908+
["NAME:Selections", "Selections:=", f"{sheet},{object}"],
5909+
[
5910+
"NAME:ProjectSheetParameters",
5911+
"Thickness:=",
5912+
self._app.value_with_units(thickness),
5913+
"DraftAngle:=",
5914+
self._app.value_with_units(draft_angle, angle_unit),
5915+
"KeepOriginals:=",
5916+
keep_originals,
5917+
],
5918+
)
5919+
unclassified_new = [i for i in self.unclassified_objects if i not in unclassified]
5920+
if unclassified_new:
5921+
self.logger.error("Failed to Project Sheet. Reverting to original objects.")
5922+
self._odesign.Undo()
5923+
return False
5924+
except Exception:
5925+
self.logger.error("Failed to Project Sheet.")
5926+
return False
5927+
5928+
if not keep_originals:
5929+
self.cleanup_objects()
5930+
return True
5931+
58735932
@pyaedt_function_handler(input_objects_list="assignment")
58745933
def heal_objects(
58755934
self,

tests/system/general/test_02_3D_modeler.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,3 +1231,83 @@ def test_pointing_to_axis(self):
12311231
assert go.is_vector_equal(x, [0.7053456158585983, 0.07053456158585983, 0.7053456158585983])
12321232
assert go.is_vector_equal(y, [0.19470872568244801, 0.9374864569895649, -0.28845737138140465])
12331233
assert go.is_vector_equal(z, [-0.681598176590997, 0.3407990882954985, 0.6475182677614472])
1234+
1235+
def test_project_sheet_success_with_single_object(self):
1236+
"""Test project sheet method with a single object."""
1237+
EXPECTED_POSITIONS = [
1238+
[5.0, -5.0, 11.0],
1239+
[5.0, -5.0, 10.0],
1240+
[-5.0, -5.0, 11.0],
1241+
[-5.0, -5.0, 10.0],
1242+
[-5.0, 10.0, 10.0],
1243+
[5.0, 10.0, 10.0],
1244+
[5.0, 11.0, 11.0],
1245+
[-5.0, 11.0, 11.0],
1246+
[5.0, 10.0, -10.0],
1247+
[-5.0, 10.0, -10.0],
1248+
[-5.0, 11.0, -11.0],
1249+
[5.0, 11.0, -11.0],
1250+
]
1251+
rect = self.aedtapp.modeler.create_rectangle(Plane.XY, [-5, -5, 15], [10, 20], "sheet_project_operation")
1252+
box = self.aedtapp.modeler.create_box([-10, -10, -10], [20, 20, 20], "box_project_operation")
1253+
1254+
assert self.aedtapp.modeler.project_sheet(rect, box, 1, keep_originals=False)
1255+
1256+
obj = self.aedtapp.modeler.get_object_from_name("sheet_project_operation")
1257+
assert obj is not None, "Expected object not found"
1258+
assert 12 == len(obj.vertices), "Object has not the number of expected vertices"
1259+
positions = [vertex.position for vertex in list(obj.vertices)]
1260+
assert sorted(EXPECTED_POSITIONS) == sorted(positions), "Object has not the expected vertices positions"
1261+
self.aedtapp.modeler.delete(self.aedtapp.modeler.object_names)
1262+
1263+
def test_project_sheet_success_with_multiple_objects(self):
1264+
"""Test project sheet method with multiple objects."""
1265+
EXPECTED_POSITIONS = [
1266+
[5.0, -5.0, 11.0],
1267+
[5.0, -5.0, 10.0],
1268+
[-5.0, -5.0, 11.0],
1269+
[-5.0, -5.0, 10.0],
1270+
[-5.0, 10.0, 10.0],
1271+
[5.0, 10.0, 10.0],
1272+
[5.0, 11.0, 11.0],
1273+
[-5.0, 11.0, 11.0],
1274+
[5.0, 10.0, -10.0],
1275+
[-5.0, 10.0, -10.0],
1276+
[-5.0, 11.0, -11.0],
1277+
[5.0, 11.0, -11.0],
1278+
[-2.0, 2, 13.0],
1279+
[-2.0, -2, 13.0],
1280+
[-2.0, -2, 11.0],
1281+
[-2.0, 2, 11.0],
1282+
[2.0, -2, 13.0],
1283+
[2.0, 2, 13.0],
1284+
[2.0, -2, 11.0],
1285+
[2.0, 2, 11.0],
1286+
[-1.0, 1.0, 10.0],
1287+
[-1.0, -1.0, 10.0],
1288+
[1.0, 1.0, 10.0],
1289+
[1.0, -1.0, 10.0],
1290+
[1.0, -1.0, 12.0],
1291+
[-1.0, -1.0, 12.0],
1292+
[1.0, 1.0, 12.0],
1293+
[-1.0, 1.0, 12.0],
1294+
]
1295+
rect = self.aedtapp.modeler.create_rectangle(Plane.XY, [-5, -5, 15], [10, 20], "sheet_project_operation")
1296+
box_0 = self.aedtapp.modeler.create_box([-10, -10, -10], [20, 20, 20], "box_project_operation_0")
1297+
box_1 = self.aedtapp.modeler.create_box([-1, -1, 10], [2, 2, 2], "box_project_operation_1")
1298+
1299+
assert self.aedtapp.modeler.project_sheet(rect, [box_0, box_1], 1, keep_originals=False)
1300+
1301+
obj = self.aedtapp.modeler.get_object_from_name("sheet_project_operation")
1302+
assert obj is not None, "Expected object not found"
1303+
assert 28 == len(obj.vertices), "Object has not the number of expected vertices"
1304+
positions = [vertex.position for vertex in list(obj.vertices)]
1305+
assert sorted(EXPECTED_POSITIONS) == sorted(positions), "Object has not the expected vertices positions"
1306+
self.aedtapp.modeler.delete(self.aedtapp.modeler.object_names)
1307+
1308+
def test_project_sheet_failure(self):
1309+
rect = self.aedtapp.modeler.create_rectangle(Plane.XY, [-5, -5, 15], [10, 20], "sheet_project_operation")
1310+
box_0 = self.aedtapp.modeler.create_box([-10, -10, -10], [20, 20, 20], "box_project_operation_0")
1311+
1312+
assert not self.aedtapp.modeler.project_sheet(rect, box_0, 5, "10deg")
1313+
self.aedtapp.modeler.delete(self.aedtapp.modeler.object_names)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
4+
# SPDX-License-Identifier: MIT
5+
#
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
25+
import logging
26+
from unittest.mock import MagicMock
27+
from unittest.mock import PropertyMock
28+
from unittest.mock import patch
29+
30+
import pytest
31+
32+
from ansys.aedt.core import Hfss
33+
from ansys.aedt.core.modeler.cad.primitives import GeometryModeler
34+
35+
36+
@pytest.fixture
37+
def mock_hfss_app():
38+
"""Fixture used to mock the creation of a Maxwell instance."""
39+
with patch("ansys.aedt.core.hfss.Hfss.__init__", lambda x: None):
40+
mock_instance = Hfss()
41+
mock_instance._logger = logging.getLogger(__name__)
42+
mock_instance._oeditor = PropertyMock(return_value=MagicMock())
43+
mock_instance.value_with_units = MagicMock(return_value="1mm")
44+
mock_instance._odesign = MagicMock()
45+
yield mock_instance
46+
47+
48+
@patch("ansys.aedt.core.modeler.cad.primitives.GeometryModeler.unclassified_objects", new_callable=PropertyMock)
49+
def test_project_object_failure(mock_unclassified_objects, mock_hfss_app, caplog: pytest.LogCaptureFixture):
50+
mock_unclassified_objects.side_effect = [[], [MagicMock()]]
51+
gm = GeometryModeler(mock_hfss_app)
52+
53+
assert not gm.project_sheet("rect", "box", 1)
54+
assert any(
55+
"Failed to Project Sheet. Reverting to original objects." in record.getMessage() for record in caplog.records
56+
)

0 commit comments

Comments
 (0)