diff --git a/doc/changelog.d/651.added.md b/doc/changelog.d/651.added.md new file mode 100644 index 000000000..1bfb6fb50 --- /dev/null +++ b/doc/changelog.d/651.added.md @@ -0,0 +1 @@ +Add thermic source diff --git a/examples/core/source.py b/examples/core/source.py index e30e77aca..965abd1b3 100644 --- a/examples/core/source.py +++ b/examples/core/source.py @@ -17,6 +17,7 @@ SourceLuminaire, SourceRayFile, SourceSurface, + SourceThermic, ) # - @@ -254,6 +255,22 @@ def create_face(body): source5.delete() # - +# ### Thermic source + +# + +source6 = p.create_source(name="Thermic.1", feature_type=SourceThermic) +source6.set_emissive_faces(geometries=[(GeoRef.from_native_link("TheBodyB/TheFaceF"), False)]) +print(source6) + +source6.commit() +print(source6) +# - + +# + +source6.delete() +# - + + # When creating sources, this creates some intermediate objects (spectrums, intensity templates). # # Deleting a source does not delete in cascade those objects diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index bbb1e88fe..80deebc2c 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -58,6 +58,7 @@ SourceLuminaire, SourceRayFile, SourceSurface, + SourceThermic, ) from ansys.speos.core.speos import Speos @@ -158,7 +159,9 @@ def create_source( description: str = "", feature_type: type = SourceSurface, metadata: Optional[Mapping[str, str]] = None, - ) -> Union[SourceSurface, SourceRayFile, SourceLuminaire, SourceAmbientNaturalLight]: + ) -> Union[ + SourceSurface, SourceRayFile, SourceLuminaire, SourceAmbientNaturalLight, SourceThermic + ]: """Create a new Source feature. Parameters @@ -224,9 +227,16 @@ def create_source( description=description, metadata=metadata, ) + case "SourceThermic": + feature = SourceThermic( + project=self, + name=name, + description=description, + metadata=metadata, + ) case _: msg = "Requested feature {} does not exist in supported list {}".format( - feature_type, [SourceSurface, SourceLuminaire, SourceRayFile] + feature_type, [SourceSurface, SourceLuminaire, SourceRayFile, SourceThermic] ) raise TypeError(msg) self._features.append(feature) @@ -438,6 +448,7 @@ def find( SourceSurface, SourceLuminaire, SourceRayFile, + SourceThermic, SensorIrradiance, SensorRadiance, SensorCamera, @@ -815,6 +826,13 @@ def _fill_features(self): source_instance=src_inst, default_values=False, ) + elif src_inst.HasField("thermic_properties"): + src_feat = SourceThermic( + project=self, + name=src_inst.name, + source_instance=src_inst, + default_values=False, + ) if src_feat is not None: self._features.append(src_feat) diff --git a/src/ansys/speos/core/source.py b/src/ansys/speos/core/source.py index 583765fc6..f1e34d8e0 100644 --- a/src/ansys/speos/core/source.py +++ b/src/ansys/speos/core/source.py @@ -45,6 +45,7 @@ from ansys.speos.core.kernel.client import SpeosClient from ansys.speos.core.kernel.scene import ProtoScene from ansys.speos.core.kernel.source_template import ProtoSourceTemplate +from ansys.speos.core.opt_prop import OptProp from ansys.speos.core.spectrum import Spectrum @@ -1255,6 +1256,243 @@ def delete(self) -> SourceSurface: return self +class SourceThermic(BaseSource): + """ThermicSource. + + By default, a flux from intensity file is chosen, with an incandescent spectrum. + + Parameters + ---------- + project : ansys.speos.core.project.Project + Project that will own the feature. + name : str + Name of the feature. + description : str + Description of the feature. + By default, ``""``. + metadata : Optional[Mapping[str, str]] + Metadata of the feature. + By default, ``{}``. + default_values : bool + Uses default values when True. + """ + + def __init__( + self, + project: project.Project, + name: str, + description: str = "", + metadata: Optional[Mapping[str, str]] = None, + source_instance: Optional[ProtoScene.SourceInstance] = None, + default_values: bool = True, + ) -> None: + if metadata is None: + metadata = {} + + super().__init__( + project=project, + name=name, + description=description, + metadata=metadata, + source_instance=source_instance, + ) + self._speos_client = self._project.client + self._name = name + + self._intensity = Intensity( + speos_client=self._speos_client, + name=name + ".Intensity", + key=self._source_template.thermic.intensity_guid, + ) + + self._sop = OptProp( + project=self._project, + name=self._name, + ) + + if default_values: + self.set_emissive_faces(geometries=[]) + self.emissive_faces_temperature = 2000 + self._sop.set_surface_mirror(0) + + def set_emissive_faces( + self, geometries: List[tuple[Union[GeoRef, face.Face, body.Body], bool]] + ) -> SourceThermic: + """Set emssive faces for thermic source. + + Parameters + ---------- + geometries : List[tuple[ansys.speos.core.geo_ref.GeoRef, bool]] + List of (face, reverseNormal). + + Returns + ------- + ansys.speos.core.source.SourceThermic + Thermic source. + """ + self._source_instance.thermic_properties.emissive_faces_properties.ClearField("geo_paths") + if geometries != []: + my_list = [ + ProtoScene.GeoPath(geo_path=gr.to_native_link(), reverse_normal=reverse_normal) + for (gr, reverse_normal) in geometries + ] + self._source_instance.thermic_properties.emissive_faces_properties.geo_paths.extend( + my_list + ) + self._source_template.thermic.emissives_faces.SetInParent() + return self + + @property + def emissive_faces_temperature(self) -> float: + """Get temperature settings for emissive faces. + + Returns + ------- + float + temperature settings for emissive faces. + + """ + if self._source_template.thermic.HasField("emissives_faces"): + return self._source_template.thermic.emissives_faces.temperature + else: + raise AttributeError("This feature is not defined as emissive faces.") + + @emissive_faces_temperature.setter + def emissive_faces_temperature(self, value: float) -> None: + """Set temperature settings for emissive faces. + + Parameters + ---------- + value: float + temperature settings for emissive faces. + + Returns + ------- + None + + """ + self._source_template.thermic.emissives_faces.temperature = value + + def set_temperature_field(self) -> SourceThermic: + """Set thermic source in temperature field mode. + + Parameters + ---------- + None + + Returns + ------- + ansys.speos.core.source.SourceThermic + Thermic source. + """ + if not self._source_template.thermic.HasField("temperature_field"): + self._source_template.thermic.temperature_field.SetInParent() + if self._source_instance.thermic_properties.temperature_field_properties.axis_plane is None: + axis_plane = [0, 0, 0, 1, 0, 0, 0, 1, 0] + self._source_instance.thermic_properties.temperature_field_properties.axis_plane[:] = ( + axis_plane + ) + return self + + @property + def temperature_field_uri(self) -> str: + """Get temperature field file uri. + + Returns + ------- + string + temperature field file uri. + + """ + if self._source_template.thermic.HasField("temperature_field"): + return self._source_template.thermic.temperature_field.temperature_field_uri + else: + raise AttributeError("This feature is not defined with temperature field.") + + def set_temperature_field_uri(self, file: str) -> None: + """Set temperature field file path. + + Parameters + ---------- + file: str + temperature field file path. + + Returns + ------- + None + + """ + self._source_template.thermic.temperature_field.temperature_field_uri = file + + @property + def sop(self) -> OptProp: + """Get SOP for thermic source in temperature field mode. + + Returns + ------- + ansys.speos.core.opt_prop.OptProp + Surface Optical property. + + """ + if self._source_template.thermic.HasField("temperature_field"): + return self._sop + else: + raise AttributeError("This feature is not defined with temperature field.") + + @sop.setter + def sop(self, new_sop: OptProp) -> None: + """Set SOP for thermic source in temperature field mode. + + Parameters + ---------- + ansys.speos.core.opt_prop.OptProp + Surface Optical property. + + Returns + ------- + None + + """ + self._sop = new_sop + self._source_template.thermic.temperature_field.sop_guid = self._sop.sop_template_link.key + + def commit(self) -> SourceThermic: + """Save feature: send the local data to the speos server database. + + Returns + ------- + ansys.speos.core.source.SourceThermic + Thermic source feature. + """ + # intensity + self._intensity.commit() + self._source_template.thermic.intensity_guid = self._intensity.intensity_template_link.key + + # sop + if self._source_template.thermic.HasField("temperature_field"): + self._sop.commit() + self._source_template.thermic.temperature_field.sop_guid = ( + self._sop.sop_template_link.key + ) + + # source base + super().commit() + return self + + def reset(self) -> SourceThermic: + """Reset feature: override local data by the one from the speos server database. + + Returns + ------- + ansys.speos.core.source.SourceThermic + Thermic source feature. + """ + self._intensity.reset() + # source base + super().reset() + return self + + class BaseSourceAmbient(BaseSource): """ Super Class for ambient sources. diff --git a/tests/core/test_source.py b/tests/core/test_source.py index 3a679b110..0fcf4fd26 100644 --- a/tests/core/test_source.py +++ b/tests/core/test_source.py @@ -31,6 +31,7 @@ SourceLuminaire, SourceRayFile, SourceSurface, + SourceThermic, ) from tests.conftest import test_path @@ -525,6 +526,53 @@ def test_create_natural_light_source(speos: Speos): source2.delete() +def test_create_thermic_source(speos: Speos): + """Test creation of thermic source.""" + p = Project(speos=speos) + + source1 = p.create_source(name="Thermic Source", feature_type=SourceThermic) + # Geometry + root_part = p.create_root_part().commit() + body_b1 = root_part.create_body(name="TheBodyB1").commit() + face_b1_f1 = ( + body_b1.create_face(name="TheFaceF1") + .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() + ) + # Optical Property + p.create_optical_property("OpaqueMirror50").set_volume_opaque().set_surface_mirror( + reflectance=50 + ).set_geometries(geometries=[body_b1.geo_path]).commit() + + source1.set_emissive_faces(geometries=[(face_b1_f1.geo_path, False)]) + source1.commit() + + assert source1._source_template.thermic.HasField("emissives_faces") + assert source1._source_template.thermic.emissives_faces.temperature == 2000 + assert source1._source_instance.HasField("thermic_properties") + assert source1._source_instance.thermic_properties.HasField("emissive_faces_properties") + assert ( + source1._source_instance.thermic_properties.emissive_faces_properties.geo_paths[0].geo_path + == "TheBodyB1/TheFaceF1" + ) + assert ( + source1._source_instance.thermic_properties.emissive_faces_properties.geo_paths[ + 0 + ].reverse_normal + is False + ) + + intensity = speos.client[source1.source_template_link.get().thermic.intensity_guid] + assert intensity.get().HasField("cos") + assert intensity.get().cos.N == 1 + + source1.emissive_faces_temperature = 3500 + assert source1._source_template.thermic.emissives_faces.temperature == 3500 + assert source1.emissive_faces_temperature == 3500 + + def test_keep_same_internal_feature(speos: Speos): """Test regarding source internal features (like spectrum, intensity). @@ -581,9 +629,24 @@ def test_keep_same_internal_feature(speos: Speos): source3.commit() assert source3.source_template_link.get().rayfile.spectrum_guid == spectrum_guid + # THERMIC SOURCE + source4 = SourceThermic(project=p, name="Thermic.1") + source4.emissive_faces_temperature = 2000 + source4.commit() + + # Modify field type + source4.set_temperature_field() + uri = str(Path(test_path) / "dummy.OPTTemperatureField") + source4.set_temperature_field_uri(uri) + sop_guid = source4._source_template.thermic.temperature_field.sop_guid + assert source4._source_template.thermic.temperature_field.temperature_field_uri == uri + assert source4.temperature_field_uri == uri + assert source4._source_template.thermic.temperature_field.sop_guid == sop_guid + source1.delete() source2.delete() source3.delete() + source4.delete() def test_commit_source(speos: Speos):