Repository: ansys/pyspeos
Pull request #551: feat: geopath property
Author: login: StefanThoene
Labels: name: enhancement
description: New features or code improvements
Created: 2025-04-14T14:11:35Z | Merged: 2025-04-30T13:20:21Z

## Description
**provide easy access to geometry path information from part body face instances**

## Issue linked
#550 

## Checklist
- [x] I have tested my changes locally.
- [x] I have added necessary documentation or updated existing documentation.
- [x] I have followed the coding style guidelines of this project.
- [x] I have added appropriate tests (unit, integration, system).
- [x] I have reviewed my changes before submitting this pull request.
- [x] I have linked the issue or issues that are solved by the PR if any.
- [x] I have assigned this PR to myself.
- [x] I have made sure that the title of my PR follows [Conventional commits style](https://www.conventionalcommits.org/en/v1.0.0/#summary) (e.g. ``feat: add optical property``)
- [x] I have agreed with the Contributor License Agreement ([CLA](https://developer.ansys.com/form/cla-acceptance)).



Diff:
diff --git a/doc/changelog.d/551.added.md b/doc/changelog.d/551.added.md
new file mode 100644
index 000000000..06af3c969
--- /dev/null
+++ b/doc/changelog.d/551.added.md
@@ -0,0 +1 @@
+geopath property
\ No newline at end of file
diff --git a/examples/core/opt-prop.py b/examples/core/opt-prop.py
index ad85097ed..deaae03e7 100644
--- a/examples/core/opt-prop.py
+++ b/examples/core/opt-prop.py
@@ -16,7 +16,7 @@
 # +
 from pathlib import Path
 
-from ansys.speos.core import GeoRef, Project, Speos
+from ansys.speos.core import Project, Speos
 
 # -
 
@@ -34,21 +34,24 @@ def create_helper_geometries(project: Project):
     """Create bodies and faces."""
 
     def create_face(body):
-        (
+        face = (
             body.create_face(name="TheFaceF")
             .set_vertices([0, 0, 0, 1, 0, 0, 0, 1, 0])
             .set_facets([0, 1, 2])
             .set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1])
             .commit()
         )
+        return face
 
+    data = {"bodies": [], "faces": []}
     root_part = project.create_root_part().commit()
-    body_b1 = root_part.create_body(name="TheBodyB").commit()
-    body_b2 = root_part.create_body(name="TheBodyC").commit()
-    body_b3 = root_part.create_body(name="TheBodyD").commit()
-    body_b4 = root_part.create_body(name="TheBodyE").commit()
-    for b in [body_b1, body_b2, body_b3, body_b4]:
-        create_face(b)
+    data["bodies"].append(root_part.create_body(name="TheBodyB").commit())
+    data["bodies"].append(root_part.create_body(name="TheBodyC").commit())
+    data["bodies"].append(root_part.create_body(name="TheBodyD").commit())
+    data["bodies"].append(root_part.create_body(name="TheBodyE").commit())
+    for b in data["bodies"]:
+        data["faces"].append(create_face(b))
+    return data
 
 
 # ## Model Setup
@@ -80,7 +83,9 @@ def create_face(body):
 
 p = Project(speos=speos)
 print(p)
-create_helper_geometries(p)
+data = create_helper_geometries(p)
+bodies = data["bodies"]
+faces = data["faces"]
 
 # ## Create VOP (volume optical property)
 #
@@ -93,8 +98,8 @@ def create_face(body):
 # This optical property will be applied to two bodies named : "TheBodyB" and "TheBodyC".
 op1.set_geometries(
     geometries=[
-        GeoRef.from_native_link(geopath="TheBodyB"),
-        GeoRef.from_native_link(geopath="TheBodyC"),
+        bodies[0],
+        bodies[1],
     ]
 )
 print(op1)
@@ -120,8 +125,8 @@ def create_face(body):
 # This optical property will be applied to two bodies named : "TheBodyD" and "TheBodyE".
 op2.set_geometries(
     geometries=[
-        GeoRef.from_native_link(geopath="TheBodyD"),
-        GeoRef.from_native_link(geopath="TheBodyE"),
+        bodies[2],
+        bodies[3],
     ]
 )
 op2.commit()
@@ -138,7 +143,7 @@ def create_face(body):
 op3 = p.create_optical_property(name="Material.FOP")
 op3.set_surface_mirror(reflectance=90)  # SOP : mirror
 # This optical property will be applied a face from TheBodyD named : "TheFaceF".
-op3.set_geometries(geometries=[GeoRef.from_native_link(geopath="TheBodyD/TheFaceF")])
+op3.set_geometries(geometries=[faces[2]])
 op3.commit()
 print(op3)
 
diff --git a/examples/core/simulation.py b/examples/core/simulation.py
index a897be3e5..16dc60651 100644
--- a/examples/core/simulation.py
+++ b/examples/core/simulation.py
@@ -13,7 +13,7 @@
 # +
 from pathlib import Path
 
-from ansys.speos.core import GeoRef, Project, Speos
+from ansys.speos.core import Project, Speos
 from ansys.speos.core.simulation import SimulationInteractive, SimulationInverse
 
 # -
@@ -48,7 +48,7 @@
 # is running as a service. In this example, the server and
 # client are the same machine.
 
-speos = Speos(host="localhost", port=50098)
+speos = Speos(host=HOSTNAME, port=GRPC_PORT)
 
 # ### Create a new project
 #
@@ -66,9 +66,13 @@
 # ### Prepare the root part
 
 root_part = p.create_root_part()
-root_part.create_body(name="Body.1").create_face(name="Face.1").set_vertices(
-    [0, 1, 2, 0, 2, 2, 1, 2, 2]
-).set_facets([0, 1, 2]).set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1])
+body_1 = root_part.create_body(name="Body.1")
+face_1 = (
+    body_1.create_face(name="Face.1")
+    .set_vertices([0, 1, 2, 0, 2, 2, 1, 2, 2])
+    .set_facets([0, 1, 2])
+    .set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1])
+)
 root_part.commit()
 
 # ### Prepare an optical property
@@ -79,7 +83,7 @@
 
 # Choose the geometry for this optical property : Body.1
 
-opt_prop.set_geometries(geometries=[GeoRef.from_native_link(geopath="Body.1")])
+opt_prop.set_geometries(geometries=[body_1])
 opt_prop.commit()
 
 
@@ -95,7 +99,7 @@
 # ### Prepare a surface source
 
 source1 = p.create_source(name=SOURCE_NAME)
-source1.set_exitance_constant(geometries=[(GeoRef.from_native_link(geopath="Body.1/Face.1"), True)])
+source1.set_exitance_constant(geometries=[(face_1, True)])
 # define a spectrum which is not monochromatic so it can be used in both direct and inverse
 # simulation
 source1.set_spectrum().set_blackbody()
diff --git a/src/ansys/speos/core/body.py b/src/ansys/speos/core/body.py
index 3be692f0b..6382085d8 100644
--- a/src/ansys/speos/core/body.py
+++ b/src/ansys/speos/core/body.py
@@ -29,6 +29,7 @@
 
 from ansys.speos.core import proto_message_utils
 import ansys.speos.core.face as face
+from ansys.speos.core.geo_ref import GeoRef
 from ansys.speos.core.kernel.body import ProtoBody
 from ansys.speos.core.kernel.client import SpeosClient
 import ansys.speos.core.part as part
@@ -81,6 +82,14 @@ def __init__(
 
         self._geom_features = []
 
+    @property
+    def geo_path(self) -> GeoRef:
+        """Geometry path to be used within other speos objects."""
+        geo_paths = [self._name]
+        if isinstance(self._parent_part, part.Part.SubPart):
+            geo_paths.insert(0, self._parent_part.geo_path.metadata["GeoPath"])
+        return GeoRef.from_native_link("/".join(geo_paths))
+
     def create_face(
         self,
         name: str,
diff --git a/src/ansys/speos/core/face.py b/src/ansys/speos/core/face.py
index e39110268..62c199e75 100644
--- a/src/ansys/speos/core/face.py
+++ b/src/ansys/speos/core/face.py
@@ -28,6 +28,7 @@
 
 from ansys.speos.core import proto_message_utils
 import ansys.speos.core.body as body
+from ansys.speos.core.geo_ref import GeoRef
 from ansys.speos.core.kernel.client import SpeosClient
 from ansys.speos.core.kernel.face import ProtoFace
 
@@ -76,6 +77,15 @@ def __init__(
         # Create local Face
         self._face = ProtoFace(name=name, description=description, metadata=metadata)
 
+    @property
+    def geo_path(self) -> GeoRef:
+        """Geometry path to be used within other speos objects."""
+        geo_paths = [self._name]
+        parent = self._parent_body
+        if isinstance(parent, body.Body):
+            geo_paths.insert(0, parent.geo_path.metadata["GeoPath"])
+        return GeoRef.from_native_link("/".join(geo_paths))
+
     def set_vertices(self, values: List[float]) -> Face:
         """Set the face vertices.
 
diff --git a/src/ansys/speos/core/opt_prop.py b/src/ansys/speos/core/opt_prop.py
index da3dd3e8a..602e2594a 100644
--- a/src/ansys/speos/core/opt_prop.py
+++ b/src/ansys/speos/core/opt_prop.py
@@ -24,13 +24,16 @@
 from __future__ import annotations
 
 from difflib import SequenceMatcher
-from typing import List, Mapping, Optional
+from typing import List, Mapping, Optional, Union
 import uuid
 
+import ansys.speos.core.body as body
+import ansys.speos.core.face as face
 from ansys.speos.core.geo_ref import GeoRef
 from ansys.speos.core.kernel.scene import ProtoScene
 from ansys.speos.core.kernel.sop_template import ProtoSOPTemplate
 from ansys.speos.core.kernel.vop_template import ProtoVOPTemplate
+import ansys.speos.core.part as part
 import ansys.speos.core.project as project
 import ansys.speos.core.proto_message_utils as proto_message_utils
 
@@ -264,7 +267,10 @@ def set_volume_library(self, path: str) -> OptProp:
         self._vop_template.library.material_file_uri = path
         return self
 
-    def set_geometries(self, geometries: Optional[List[GeoRef]] = None) -> OptProp:
+    def set_geometries(
+        self,
+        geometries: Optional[List[Union[GeoRef, body.Body, face.Face, part.Part.SubPart]]] = None,
+    ) -> OptProp:
         """Select geometries on which the optical properties will be applied.
 
         Parameters
@@ -281,8 +287,17 @@ def set_geometries(self, geometries: Optional[List[GeoRef]] = None) -> OptProp:
         if geometries is None:
             self._material_instance.ClearField("geometries")
         else:
+            geo_paths = []
+            for gr in geometries:
+                if isinstance(gr, GeoRef):
+                    geo_paths.append(gr)
+                elif isinstance(gr, (face.Face, body.Body, part.Part.SubPart)):
+                    geo_paths.append(gr.geo_path)
+                else:
+                    msg = f"Type {type(gr)} is not supported as Optical property geometry input."
+                    raise TypeError(msg)
             self._material_instance.geometries.geo_paths[:] = [
-                gr.to_native_link() for gr in geometries
+                gp.to_native_link() for gp in geo_paths
             ]
         return self
 
diff --git a/src/ansys/speos/core/part.py b/src/ansys/speos/core/part.py
index eabe4ab9c..85e3a8280 100644
--- a/src/ansys/speos/core/part.py
+++ b/src/ansys/speos/core/part.py
@@ -31,6 +31,7 @@
 from ansys.speos.core import proto_message_utils
 import ansys.speos.core.body as body
 import ansys.speos.core.face as face
+from ansys.speos.core.geo_ref import GeoRef
 from ansys.speos.core.kernel.client import SpeosClient
 from ansys.speos.core.kernel.part import ProtoPart
 import ansys.speos.core.project as project
@@ -100,6 +101,14 @@ def __init__(
 
             self._geom_features = []
 
+        @property
+        def geo_path(self) -> GeoRef:
+            """Geometry path to be used within other speos objects."""
+            geo_paths = [self._name]
+            if isinstance(self._parent_part, Part.SubPart):
+                geo_paths.insert(0, self._parent_part.geo_path.metadata["GeoPath"])
+            return GeoRef.from_native_link("/".join(geo_paths))
+
         def create_body(
             self,
             name: str,
diff --git a/src/ansys/speos/core/source.py b/src/ansys/speos/core/source.py
index 9e5352462..2edafd477 100644
--- a/src/ansys/speos/core/source.py
+++ b/src/ansys/speos/core/source.py
@@ -32,6 +32,8 @@
     project as project,
     proto_message_utils as proto_message_utils,
 )
+import ansys.speos.core.body as body
+import ansys.speos.core.face as face
 from ansys.speos.core.geo_ref import GeoRef
 from ansys.speos.core.intensity import Intensity
 from ansys.speos.core.kernel.client import SpeosClient
@@ -959,7 +961,9 @@ def set_intensity(self) -> Intensity:
 
         return self._intensity
 
-    def set_exitance_constant(self, geometries: List[tuple[GeoRef, bool]]) -> SourceSurface:
+    def set_exitance_constant(
+        self, geometries: List[tuple[Union[GeoRef, face.Face, body.Body], bool]]
+    ) -> SourceSurface:
         """Set existence constant.
 
         Parameters
@@ -979,12 +983,25 @@ def set_exitance_constant(self, geometries: List[tuple[GeoRef, bool]]) -> Source
             "geo_paths"
         )
         if geometries != []:
-            my_list = [
-                ProtoScene.GeoPath(geo_path=gr.to_native_link(), reverse_normal=reverse_normal)
-                for (gr, reverse_normal) in geometries
-            ]
+            geo_paths = []
+            for gr, reverse_normal in geometries:
+                if isinstance(gr, GeoRef):
+                    geo_paths.append(
+                        ProtoScene.GeoPath(
+                            geo_path=gr.to_native_link(), reverse_normal=reverse_normal
+                        )
+                    )
+                elif isinstance(gr, (face.Face, body.Body)):
+                    geo_paths.append(
+                        ProtoScene.GeoPath(
+                            geo_path=gr.geo_path.to_native_link(), reverse_normal=reverse_normal
+                        )
+                    )
+                else:
+                    msg = f"Type {type(gr)} is not supported as Surface Source geometry input."
+                    raise TypeError(msg)
             self._source_instance.surface_properties.exitance_constant_properties.geo_paths.extend(
-                my_list
+                geo_paths
             )
         return self
 
diff --git a/tests/core/test_opt_prop.py b/tests/core/test_opt_prop.py
index 4e3795821..e27cc3288 100644
--- a/tests/core/test_opt_prop.py
+++ b/tests/core/test_opt_prop.py
@@ -148,16 +148,19 @@ def test_reset_optical_property(speos: Speos):
 
     root_part = p.create_root_part()
     body_b = root_part.create_body(name="TheBodyB")
-    body_b.create_face(name="TheFaceF").set_vertices([0, 0, 0, 1, 0, 0, 0, 1, 0]).set_facets(
-        [0, 1, 2]
-    ).set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1])
+    (
+        body_b.create_face(name="TheFaceF")
+        .set_vertices([0, 0, 0, 1, 0, 0, 0, 1, 0])
+        .set_facets([0, 1, 2])
+        .set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1])
+    )
     root_part.commit()
 
     # Create + commit
     op1 = (
         p.create_optical_property(name="Material.1")
         .set_volume_opaque()
-        .set_geometries(geometries=[GeoRef.from_native_link("TheBodyB")])
+        .set_geometries(geometries=[body_b.geo_path])
         .commit()
     )
     assert op1.vop_template_link is not None
@@ -233,9 +236,12 @@ def test_get_optical_property(speos: Speos, capsys):
 
     root_part = p.create_root_part()
     body_a = root_part.create_body(name="TheBodyA")
-    body_a.create_face(name="TheFaceF").set_vertices([0, 0, 0, 1, 0, 0, 0, 1, 0]).set_facets(
-        [0, 1, 2]
-    ).set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1])
+    face = (
+        body_a.create_face(name="TheFaceF")
+        .set_vertices([0, 0, 0, 1, 0, 0, 0, 1, 0])
+        .set_facets([0, 1, 2])
+        .set_normals([0, 0, 1, 0, 0, 1, 0, 0, 1])
+    )
     body_b = root_part.create_body(name="TheBodyB")
     body_b.create_face(name="TheFaceF").set_vertices([0, 0, 0, 1, 0, 0, 0, 1, 0]).set_facets(
         [0, 1, 2]
@@ -245,7 +251,7 @@ def test_get_optical_property(speos: Speos, capsys):
     op1 = (
         p.create_optical_property(name="Material.1")
         .set_volume_opaque()
-        .set_geometries(geometries=[GeoRef.from_native_link("TheBodyA")])
+        .set_geometries(geometries=[body_a])
         .commit()
     )
 
@@ -269,7 +275,7 @@ def test_get_optical_property(speos: Speos, capsys):
         p.create_optical_property(name="OpticalProperty2")
         .set_volume_optic(index=1.7, absorption=0.01, constringence=55)
         .set_surface_opticalpolished()
-        .set_geometries(geometries=[GeoRef.from_native_link("TheBodyB")])
+        .set_geometries(geometries=[body_b])
         .commit()
     )
 
@@ -292,6 +298,7 @@ def test_get_optical_property(speos: Speos, capsys):
         p.create_optical_property(name="OpticalProperty3")
         .set_volume_none()
         .set_surface_library(path=str(Path(test_path) / "R_test.anisotropicbsdf"))
+        .set_geometries(geometries=[face])
         .commit()
     )
 
diff --git a/tests/core/test_part.py b/tests/core/test_part.py
index a7ff496e3..b001219c2 100644
--- a/tests/core/test_part.py
+++ b/tests/core/test_part.py
@@ -51,7 +51,7 @@ def test_create_body(speos: Speos):
     # Add empty body
     body1 = root_part.create_body(name="Body.1").commit()
     assert len(root_part._geom_features) == 1
-
+    assert body1.geo_path.metadata["GeoPath"] == "Body.1"
     assert len(root_part.part_link.get().body_guids) == 1
     assert root_part.part_link.get().body_guids[0] == body1.body_link.key
 
@@ -59,7 +59,7 @@ def test_create_body(speos: Speos):
     body2 = root_part.create_body(name="Body.2")
     root_part.commit()
     assert len(root_part._geom_features) == 2
-
+    assert body2.geo_path.metadata["GeoPath"] == "Body.2"
     assert len(root_part.part_link.get().body_guids) == 2
     assert root_part.part_link.get().body_guids[1] == body2.body_link.key
 
@@ -84,7 +84,7 @@ def test_create_face(speos: Speos):
     root_part.commit()
     assert len(body1._geom_features) == 1
     assert len(body1.body_link.get().face_guids) == 1
-
+    assert face0.geo_path.metadata["GeoPath"] == "Body.1/TheFaceF"
     # Add a face
     face1 = (
         body1.create_face(name="Face.1")
@@ -94,7 +94,7 @@ def test_create_face(speos: Speos):
         .commit()
     )
     assert len(body1._geom_features) == 2
-
+    assert face1.geo_path.metadata["GeoPath"] == "Body.1/Face.1"
     assert len(body1.body_link.get().face_guids) == 2
     assert body1.body_link.get().face_guids[1] == face1.face_link.key
     assert face1.face_link.get().vertices == [0, 1, 0, 0, 2, 0, 1, 2, 0]
@@ -110,7 +110,7 @@ def test_create_face(speos: Speos):
         .commit()
     )
     assert len(body1._geom_features) == 3
-
+    assert face2.geo_path.metadata["GeoPath"] == "Body.1/Face.2"
     assert len(body1.body_link.get().face_guids) == 3
     assert body1.body_link.get().face_guids[2] == face2.face_link.key
     assert face2.face_link.get().vertices == [0, 0, 0, 1, 0, 0, 0, 1, 0]
@@ -140,6 +140,7 @@ def test_create_subpart(speos: Speos):
     )
     assert len(root_part._geom_features) == 1
     assert len(root_part.part_link.get().parts) == 1
+    assert sp1.geo_path.metadata["GeoPath"] == "SubPart.1"
     assert root_part.part_link.get().parts[0] == sp1._part_instance
     assert sp1._part_instance.axis_system == [
         5,
@@ -168,6 +169,7 @@ def test_create_subpart(speos: Speos):
     root_part.commit()
     assert len(root_part._geom_features) == 2
     assert len(root_part.part_link.get().parts) == 2
+    assert sp2.geo_path.metadata["GeoPath"] == "SubPart.2"
     assert root_part.part_link.get().parts[1] == sp2._part_instance
     assert sp2._part_instance.axis_system == [
         15,
@@ -211,7 +213,7 @@ def test_create_subpart_body(speos: Speos):
     # Add empty body
     body1 = sp1.create_body(name="Body.1").commit()
     assert len(sp1._geom_features) == 1
-
+    assert body1.geo_path.metadata["GeoPath"] == "SubPart.1/Body.1"
     assert len(sp1.part_link.get().body_guids) == 1
     assert sp1.part_link.get().body_guids[0] == body1.body_link.key
 
@@ -219,7 +221,7 @@ def test_create_subpart_body(speos: Speos):
     body2 = sp1.create_body(name="Body.2")
     root_part.commit()
     assert len(sp1._geom_features) == 2
-
+    assert body2.geo_path.metadata["GeoPath"] == "SubPart.1/Body.2"
     assert len(sp1.part_link.get().body_guids) == 2
     assert sp1.part_link.get().body_guids[1] == body2.body_link.key
 
@@ -266,6 +268,7 @@ def test_create_subpart_subpart(speos: Speos):
         1,
     ]
     assert sp11._part_instance.part_guid == sp11.part_link.key
+    assert sp11.geo_path.metadata["GeoPath"] == "SubPart.1/SubPart.11"
     assert (
         sp11.part_link.get().name == "SubPart.11"
     )  # part contained in a sub part gets same name as the sub part
@@ -294,6 +297,7 @@ def test_create_subpart_subpart(speos: Speos):
         1,
     ]
     assert sp12._part_instance.part_guid == sp12.part_link.key
+    assert sp12.geo_path.metadata["GeoPath"] == "SubPart.1/SubPart.12"
     assert (
         sp12.part_link.get().name == "SubPart.12"
     )  # part contained in a sub part gets same name as the sub part
diff --git a/tests/core/test_source.py b/tests/core/test_source.py
index dedaadcd5..cdca7e39b 100644
--- a/tests/core/test_source.py
+++ b/tests/core/test_source.py
@@ -384,7 +384,7 @@ def test_keep_same_internal_feature(speos: Speos):
 
     # SURFACE SOURCE
     source1 = SourceSurface(project=p, name="Surface.1")
-    source1.set_exitance_constant(geometries=[(GeoRef.from_native_link("BodyB"), False)])
+    source1.set_exitance_constant(geometries=[(body_b, False)])
     source1.commit()
     spectrum_guid = source1.source_template_link.get().surface.spectrum_guid
     intensity_guid = source1.source_template_link.get().surface.intensity_guid


Reviews:
user: login: pluAtAnsys
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-04-16T21:31:41Z
user: login: StefanThoene
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T06:26:50Z
user: login: jomadec
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T12:12:56Z
user: login: StefanThoene
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T14:29:32Z
user: login: StefanThoene
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T06:22:28Z
user: login: jomadec
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-30T08:21:11Z

Review comments:
diff_hunk: @@ -68,7 +68,7 @@ def __init__(
         parent_part: Optional[Union[part.Part, part.Part.SubPart]] = None,
     ) -> None:
         self._speos_client = speos_client
-        self._parent_part = parent_part
+        self._parent = parent_part
path: src/ansys/speos/core/body.py
user: login: pluAtAnsys
body: could I ask why this is renamed?
created_at: 2025-04-16T20:51:05Z
author_association: COLLABORATOR
diff_hunk: @@ -81,6 +81,21 @@ def __init__(
 
         self._geom_features = []
 
+    @property
+    def geo_path(self):
+        """Geometry path to be used within other speos objects."""
+        return self.__get_geo_path(self)
+
+    @staticmethod
+    def __get_geo_path(body, end=""):
path: src/ansys/speos/core/body.py
user: login: pluAtAnsys
body: no docstring. 
If using suggested method, this method is not needed. 
created_at: 2025-04-16T20:52:36Z
author_association: COLLABORATOR
diff_hunk: @@ -81,6 +81,21 @@ def __init__(
 
         self._geom_features = []
 
+    @property
+    def geo_path(self):
+        """Geometry path to be used within other speos objects."""
+        return self.__get_geo_path(self)
path: src/ansys/speos/core/body.py
user: login: pluAtAnsys
body: recursive functions is good, but there is a easy way to achieve the same:
```suggestion
        geo_paths = [self._name]
        parent = self._parent
        while isinstance(parent, Part.SubPart):
              geo_paths.insert(0, parent._name)
              parent = parent._parent
        return "/".join(geo_paths)
```
created_at: 2025-04-16T21:28:35Z
author_association: COLLABORATOR
diff_hunk: @@ -76,6 +77,21 @@ def __init__(
         # Create local Face
         self._face = ProtoFace(name=name, description=description, metadata=metadata)
 
+    @property
+    def geo_path(self):
+        """Geometry path to be used within other speos objects."""
+        return self.__get_geo_path(self)
path: src/ansys/speos/core/face.py
user: login: pluAtAnsys
body: save as above
created_at: 2025-04-16T21:30:01Z
author_association: COLLABORATOR
diff_hunk: @@ -100,6 +100,21 @@ def __init__(
 
             self._geom_features = []
 
+        @property
+        def geo_path(self):
+            """Geometry path to be used within other speos objects."""
+            return self.__get_geo_path(self)
path: src/ansys/speos/core/part.py
user: login: pluAtAnsys
body: save as above
created_at: 2025-04-16T21:30:12Z
author_association: COLLABORATOR
diff_hunk: @@ -81,6 +81,21 @@ def __init__(
 
         self._geom_features = []
 
+    @property
+    def geo_path(self):
+        """Geometry path to be used within other speos objects."""
+        return self.__get_geo_path(self)
path: src/ansys/speos/core/body.py
user: login: StefanThoene
body: that won't work for face i think you need to do it with
`not isinstance(parent, Part.Part)`
created_at: 2025-04-17T06:26:04Z
author_association: COLLABORATOR
diff_hunk: @@ -81,6 +81,21 @@ def __init__(
 
         self._geom_features = []
 
+    @property
+    def geo_path(self):
+        """Geometry path to be used within other speos objects."""
+        return self.__get_geo_path(self)
path: src/ansys/speos/core/body.py
user: login: StefanThoene
body: but great idea it is much better then my recursion
created_at: 2025-04-17T06:26:45Z
author_association: COLLABORATOR
diff_hunk: @@ -93,8 +98,8 @@ def create_face(body):
 # This optical property will be applied to two bodies named : "TheBodyB" and "TheBodyC".
 op1.set_geometries(
     geometries=[
-        GeoRef.from_native_link(geopath="TheBodyB"),
-        GeoRef.from_native_link(geopath="TheBodyC"),
+        GeoRef.from_native_link(geopath=bodies[0].geo_path),
path: examples/core/opt-prop.py
user: login: jomadec
body: @StefanThoene sugg : maybe here we could abstract even more geopaths to user by finding a way to let user to only set a list of Part/Body/Face in set_geometries method. By doing so you can actually get rid of GeoRefs and set_geometry will looks like 

`self._material_instance.geometries.geo_paths[:] = [
               gr.geo_path for gr in geometries ]`



created_at: 2025-04-25T12:04:06Z
author_association: COLLABORATOR
diff_hunk: @@ -93,8 +98,8 @@ def create_face(body):
 # This optical property will be applied to two bodies named : "TheBodyB" and "TheBodyC".
 op1.set_geometries(
     geometries=[
-        GeoRef.from_native_link(geopath="TheBodyB"),
-        GeoRef.from_native_link(geopath="TheBodyC"),
+        GeoRef.from_native_link(geopath=bodies[0].geo_path),
path: examples/core/opt-prop.py
user: login: StefanThoene
body: i did a first step the geopath property now returns a GeoRef
created_at: 2025-04-28T14:29:32Z
author_association: COLLABORATOR
diff_hunk: @@ -93,8 +98,8 @@ def create_face(body):
 # This optical property will be applied to two bodies named : "TheBodyB" and "TheBodyC".
 op1.set_geometries(
     geometries=[
-        GeoRef.from_native_link(geopath="TheBodyB"),
-        GeoRef.from_native_link(geopath="TheBodyC"),
+        bodies[0].geo_path,
+        bodies[1].geo_path,
path: examples/core/opt-prop.py
user: login: StefanThoene
body: ```suggestion
        bodies[0],
        bodies[1],
```
created_at: 2025-04-29T06:20:08Z
author_association: COLLABORATOR
diff_hunk: @@ -120,8 +125,8 @@ def create_face(body):
 # This optical property will be applied to two bodies named : "TheBodyD" and "TheBodyE".
 op2.set_geometries(
     geometries=[
-        GeoRef.from_native_link(geopath="TheBodyD"),
-        GeoRef.from_native_link(geopath="TheBodyE"),
+        bodies[2].geo_path,
+        bodies[3].geo_path,
path: examples/core/opt-prop.py
user: login: StefanThoene
body: ```suggestion
        bodies[2],
        bodies[3],
```
created_at: 2025-04-29T06:20:22Z
author_association: COLLABORATOR
diff_hunk: @@ -138,7 +143,7 @@ def create_face(body):
 op3 = p.create_optical_property(name="Material.FOP")
 op3.set_surface_mirror(reflectance=90)  # SOP : mirror
 # This optical property will be applied a face from TheBodyD named : "TheFaceF".
-op3.set_geometries(geometries=[GeoRef.from_native_link(geopath="TheBodyD/TheFaceF")])
+op3.set_geometries(geometries=[faces[2].geo_path])
path: examples/core/opt-prop.py
user: login: StefanThoene
body: ```suggestion
op3.set_geometries(geometries=[faces[2]])
```
created_at: 2025-04-29T06:20:36Z
author_association: COLLABORATOR
diff_hunk: @@ -79,7 +83,7 @@
 
 # Choose the geometry for this optical property : Body.1
 
-opt_prop.set_geometries(geometries=[GeoRef.from_native_link(geopath="Body.1")])
+opt_prop.set_geometries(geometries=[body_1.geo_path])
path: examples/core/simulation.py
user: login: StefanThoene
body: ```suggestion
opt_prop.set_geometries(geometries=[body_1])
```
created_at: 2025-04-29T06:20:54Z
author_association: COLLABORATOR
diff_hunk: @@ -95,7 +99,7 @@
 # ### Prepare a surface source
 
 source1 = p.create_source(name=SOURCE_NAME)
-source1.set_exitance_constant(geometries=[(GeoRef.from_native_link(geopath="Body.1/Face.1"), True)])
+source1.set_exitance_constant(geometries=[(face_1.geo_path, True)])
path: examples/core/simulation.py
user: login: StefanThoene
body: ```suggestion
source1.set_exitance_constant(geometries=[(face_1, True)])
```
created_at: 2025-04-29T06:21:30Z
author_association: COLLABORATOR

PR comments:
user: login: StefanThoene
created_at: 2025-04-14T14:12:35Z
body: @pluAtAnsys  can you please check what i created here before i start creating tests
author_association: COLLABORATOR
user: login: codecov[bot]
created_at: 2025-04-16T21:38:14Z
body: ## [Codecov](https://app.codecov.io/gh/ansys/pyspeos/pull/551?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) Report
Attention: Patch coverage is `91.30435%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 88.07%. Comparing base [(`c57731c`)](https://app.codecov.io/gh/ansys/pyspeos/commit/c57731c514101c3e05c4157c78315b88f906a586?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) to head [(`fd4f28d`)](https://app.codecov.io/gh/ansys/pyspeos/commit/fd4f28d1ae4421ce6bb849aa39a3ca32d68acfa7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/ansys/pyspeos/pull/551?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) | Patch % | Lines |
|---|---|---|
| [src/ansys/speos/core/opt\_prop.py](https://app.codecov.io/gh/ansys/pyspeos/pull/551?src=pr&el=tree&filepath=src%2Fansys%2Fspeos%2Fcore%2Fopt_prop.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys#diff-c3JjL2Fuc3lzL3NwZW9zL2NvcmUvb3B0X3Byb3AucHk=) | 84.61% | [2 Missing :warning: ](https://app.codecov.io/gh/ansys/pyspeos/pull/551?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) |
| [src/ansys/speos/core/source.py](https://app.codecov.io/gh/ansys/pyspeos/pull/551?src=pr&el=tree&filepath=src%2Fansys%2Fspeos%2Fcore%2Fsource.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys#diff-c3JjL2Fuc3lzL3NwZW9zL2NvcmUvc291cmNlLnB5) | 81.81% | [2 Missing :warning: ](https://app.codecov.io/gh/ansys/pyspeos/pull/551?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #551      +/-   ##
==========================================
+ Coverage   88.05%   88.07%   +0.02%     
==========================================
  Files          38       38              
  Lines        4127     4169      +42     
==========================================
+ Hits         3634     3672      +38     
- Misses        493      497       +4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ansys/pyspeos/pull/551?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansys).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: StefanThoene
created_at: 2025-04-17T12:19:13Z
body: @jomadec @echambla can you please have alook before i start reworking the tests and examples
author_association: COLLABORATOR