diff --git a/doc/changelog.d/763.added.md b/doc/changelog.d/763.added.md new file mode 100644 index 000000000..0cc4288b0 --- /dev/null +++ b/doc/changelog.d/763.added.md @@ -0,0 +1 @@ +Add supporting virtual bsdf bench diff --git a/examples/core/simulation.py b/examples/core/simulation.py index e95950587..a16d1c048 100644 --- a/examples/core/simulation.py +++ b/examples/core/simulation.py @@ -14,7 +14,11 @@ from pathlib import Path from ansys.speos.core import Project, Speos, launcher -from ansys.speos.core.simulation import SimulationInteractive, SimulationInverse +from ansys.speos.core.simulation import ( + SimulationInteractive, + SimulationInverse, + SimulationVirtualBSDF, +) # - @@ -183,4 +187,28 @@ simulation4.set_source_paths(source_paths=[SOURCE_NAME]).commit() print(simulation4) +# ### Virtual BSDF Bench simulation + +vbb = p.create_simulation(name="virtual_BSDF", feature_type=SimulationVirtualBSDF) +opt_prop.set_surface_library( + path=str(assets_data_path / "R_test.anisotropicbsdf") +).commit() # change the material property from mirror to bsdf type +vbb.axis_system = [ + 0.36, + 1.73, + 2.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, +] # change the coordinate VBSDF to body center +vbb.commit() +results = vbb.compute_CPU() +print(results) + speos.close() diff --git a/src/ansys/speos/core/project.py b/src/ansys/speos/core/project.py index b5c37fe8a..7ab4e922d 100644 --- a/src/ansys/speos/core/project.py +++ b/src/ansys/speos/core/project.py @@ -52,6 +52,7 @@ SimulationDirect, SimulationInteractive, SimulationInverse, + SimulationVirtualBSDF, ) from ansys.speos.core.source import ( SourceAmbientNaturalLight, @@ -296,6 +297,13 @@ def create_simulation( description=description, metadata=metadata, ) + case "SimulationVirtualBSDF": + feature = SimulationVirtualBSDF( + project=self, + name=name, + description=description, + metadata=metadata, + ) case _: msg = "Requested feature {} does not exist in supported list {}".format( feature_type, @@ -635,6 +643,11 @@ def _to_dict(self) -> dict: name=inside_dict["name"], feature_type=SimulationInteractive, ) + if len(sim_feat) == 0: + sim_feat = self.find( + name=inside_dict["name"], + feature_type=SimulationVirtualBSDF, + ) sim_feat = sim_feat[0] if sim_feat.job_link is None: inside_dict["simulation_properties"] = ( @@ -878,6 +891,13 @@ def _fill_features(self): simulation_instance=sim_inst, default_values=False, ) + elif simulation_template_link.HasField("virtual_bsdf_bench_simulation_template"): + sim_feat = SimulationVirtualBSDF( + project=self, + name=sim_inst.name, + simulation_instance=sim_inst, + default_values=False, + ) if sim_feat is not None: self._features.append(sim_feat) diff --git a/src/ansys/speos/core/simulation.py b/src/ansys/speos/core/simulation.py index 1d22399e9..c00e14d46 100644 --- a/src/ansys/speos/core/simulation.py +++ b/src/ansys/speos/core/simulation.py @@ -72,6 +72,203 @@ class BaseSimulation: This is a Super class, **Do not instantiate this class yourself** """ + class _SourceSampling: + """Source sampling mode. + + Parameters + ---------- + source_sampling : Union[ + simulation_template_pb2.RoughnessOnly, + simulation_template_pb2.Iridescence, + simulation_template_pb2.Isotropic, + simulation_template_pb2.Anisotropic] to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + + Notes + ----- + **Do not instantiate this class yourself**, use set_weight method available in simulation + classes. + + """ + + class _Adaptive: + """Adaptive sampling mode. + + Parameters + ---------- + uniform : simulation_template_pb2.SourceSamplingAdaptive + uniform settings to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + + """ + + def __init__( + self, + adaptive: simulation_template_pb2.SourceSamplingAdaptive, + default_values: bool = True, + stable_ctr: bool = False, + ) -> None: + if not stable_ctr: + msg = "_Adaptive class instantiated outside the class scope" + raise RuntimeError(msg) + self._adaptive = adaptive + # Default setting + + if default_values: + self.adaptive_uri = "" + + @property + def adaptive_uri(self) -> str: + """Get adaptive uri. + + Returns + ------- + str: + Adaptive sampling file uri. + + """ + return self._adaptive.file_uri + + @adaptive_uri.setter + def adaptive_uri(self, uri: Union[Path | str]) -> None: + """Set adaptive uri. + + Parameters + ---------- + uri: Union[Path | str] + Adaptive sampling file uri. + + Returns + ------- + None + + """ + self._adaptive.file_uri = str(uri) + + class _Uniform: + """Uniform sampling mode. + + Parameters + ---------- + uniform : simulation_template_pb2.SourceSamplingUniformIsotropic to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + + """ + + def __init__( + self, + uniform: simulation_template_pb2.SourceSamplingUniformIsotropic, + default_values: bool = True, + stable_ctr: bool = False, + ) -> None: + if not stable_ctr: + msg = "_Uniform class instantiated outside the class scope" + raise RuntimeError(msg) + self._uniform = uniform + # Default setting + if default_values: + self.theta_sampling = 18 + + @property + def theta_sampling(self) -> int: + """Get theta sampling. + + Returns + ------- + int + theta sampling. + """ + return self._uniform.theta_sampling + + @theta_sampling.setter + def theta_sampling(self, theta_sampling: int) -> None: + """Set theta sampling. + + Parameters + ---------- + theta_sampling: int + theta sampling. + + Returns + ------- + None + + """ + self._uniform.theta_sampling = theta_sampling + + def __init__( + self, + source_sampling: Union[ + simulation_template_pb2.RoughnessOnly, + simulation_template_pb2.Iridescence, + simulation_template_pb2.Isotropic, + simulation_template_pb2.Anisotropic, + ], + default_values: bool = True, + stable_ctr: bool = False, + ) -> None: + if not stable_ctr: + msg = "_SourceSampling class instantiated outside of the class scope" + raise RuntimeError(msg) + + self._mode = source_sampling + + self._sampling_type = None + + if default_values: + self._sampling_type = self.set_uniform() + + def set_uniform(self) -> BaseSimulation._SourceSampling._Uniform: + """Set uniform type of source sampling. + + Returns + ------- + BaseSimulation._SourceSampling._Uniform + + """ + if self._sampling_type is None and self._mode.HasField("uniform_isotropic"): + self._sampling_type = self._Uniform( + self._mode.uniform_isotropic, + default_values=False, + stable_ctr=True, + ) + if not isinstance(self._sampling_type, BaseSimulation._SourceSampling._Uniform): + self._sampling_type = self._Uniform( + self._mode.uniform_isotropic, + default_values=True, + stable_ctr=True, + ) + if self._sampling_type._uniform is not self._mode.uniform_isotropic: + self._sampling_type = self._mode.uniform_isotropic + return self._sampling_type + + def set_adaptive(self) -> BaseSimulation._SourceSampling._Adaptive: + """Set adaptive type of source sampling. + + Returns + ------- + BaseSimulation._SourceSampling._Adaptive + + """ + if self._sampling_type is None and self._mode.HasField("adaptive"): + self._sampling_type = self._Adaptive( + self._mode.adaptive, + default_values=False, + stable_ctr=True, + ) + if not isinstance(self._sampling_type, BaseSimulation._SourceSampling._Adaptive): + self._sampling_type = self._Adaptive( + self._mode.adaptive, + default_values=True, + stable_ctr=True, + ) + if self._sampling_type._adaptive is not self._mode.adaptive: + self._sampling_type = self._mode.adaptive + return self._sampling_type + class Weight: """The Weight represents the ray energy. @@ -147,6 +344,7 @@ def __init__( metadata = {} # Attribute representing the kind of simulation. self._type = None + self._template_class = None self._light_expert_changed = False if simulation_instance is None: @@ -227,6 +425,73 @@ def set_source_paths(self, source_paths: List[str]) -> BaseSimulation: # geo_paths = [gr.to_native_link() for gr in geometries] # self._simulation_instance.geometries.geo_paths[:] = geo_paths # return self + @property + def geom_distance_tolerance(self) -> float: + """Return the geometry distance tolerance. + + Returns + ------- + float + Maximum distance in mm to consider two faces as tangent. + """ + if self._template_class is not None: + return getattr(self._simulation_template, self._template_class).geom_distance_tolerance + else: + raise TypeError(f"Unknown simulation template type: {self._template_class}") + + @geom_distance_tolerance.setter + def geom_distance_tolerance(self, value: float) -> None: + """Set the geometry distance tolerance. + + Parameters + ---------- + value : float + Maximum distance in mm to consider two faces as tangent. + By default, ``0.01`` + + Returns + ------- + None + """ + if self._template_class is not None: + getattr(self._simulation_template, self._template_class).geom_distance_tolerance = value + else: + raise TypeError(f"Unknown simulation template type: {self._template_class}") + + @property + def max_impact(self) -> int: + """Return the maximum number of impacts. + + Returns + ------- + int + The maximum number of impacts. + """ + if self._template_class is not None: + return getattr(self._simulation_template, self._template_class).max_impact + else: + raise TypeError(f"Unknown simulation template type: {self._template_class}") + + @max_impact.setter + def max_impact(self, value: int) -> None: + """Define a value to determine the maximum number of ray impacts during propagation. + + When a ray has interacted N times with the geometry, the propagation of the ray stops. + + Parameters + ---------- + value : int + The maximum number of impacts. + By default, ``100``. + + Returns + ------- + None + """ + if self._template_class is not None: + getattr(self._simulation_template, self._template_class).max_impact = value + else: + raise TypeError(f"Unknown simulation template type: {self._template_class}") def export(self, export_path: Union[str, Path]) -> None: """Export simulation. @@ -665,55 +930,20 @@ def __init__( metadata=metadata, simulation_instance=simulation_instance, ) + self._template_class = "direct_mc_simulation_template" if default_values: - # Default values - self.set_geom_distance_tolerance() - self.set_max_impact() - self.set_colorimetric_standard_CIE_1931() - self.set_dispersion() # self.set_fast_transmission_gathering() self.set_ambient_material_file_uri() self.set_weight() self.set_light_expert() # Default job properties self.set_stop_condition_rays_number().set_stop_condition_duration().set_automatic_save_frequency() - - def set_geom_distance_tolerance(self, value: float = 0.01) -> SimulationDirect: - """Set the geometry distance tolerance. - - Parameters - ---------- - value : float - Maximum distance in mm to consider two faces as tangent. - By default, ``0.01``. - - Returns - ------- - ansys.speos.core.simulation.SimulationDirect - Direct simulation - """ - self._simulation_template.direct_mc_simulation_template.geom_distance_tolerance = value - return self - - def set_max_impact(self, value: int = 100) -> SimulationDirect: - """Define a value to determine the maximum number of ray impacts during propagation. - - When a ray has interacted N times with the geometry, the propagation of the ray stops. - - Parameters - ---------- - value : int - The maximum number of impacts. - By default, ``100``. - - Returns - ------- - ansys.speos.core.simulation.SimulationDirect - Direct simulation - """ - self._simulation_template.direct_mc_simulation_template.max_impact = value - return self + # Default values + self.set_colorimetric_standard_CIE_1931() + self.set_dispersion() + self.geom_distance_tolerance = 0.01 + self.max_impact = 100 def set_weight(self) -> BaseSimulation.Weight: """Activate weight. Highly recommended to fill. @@ -994,57 +1224,22 @@ def __init__( metadata=metadata, simulation_instance=simulation_instance, ) + self._template_class = "inverse_mc_simulation_template" if default_values: + # self.set_fast_transmission_gathering() + self.set_ambient_material_file_uri() + # Default job properties + self.set_stop_condition_duration().set_stop_condition_passes_number().set_automatic_save_frequency() # Default values - self.set_geom_distance_tolerance() - self.set_max_impact() + self.geom_distance_tolerance = 0.01 + self.max_impact = 100 self.set_weight() self.set_colorimetric_standard_CIE_1931() self.set_dispersion() self.set_splitting() self.set_number_of_gathering_rays_per_source() self.set_maximum_gathering_error() - # self.set_fast_transmission_gathering() - self.set_ambient_material_file_uri() - # Default job properties - self.set_stop_condition_duration().set_stop_condition_passes_number().set_automatic_save_frequency() - - def set_geom_distance_tolerance(self, value: float = 0.01) -> SimulationInverse: - """Set the geometry distance tolerance. - - Parameters - ---------- - value : float - Maximum distance in mm to consider two faces as tangent. - By default, ``0.01`` - - Returns - ------- - ansys.speos.core.simulation.SimulationInverse - Inverse simulation - """ - self._simulation_template.inverse_mc_simulation_template.geom_distance_tolerance = value - return self - - def set_max_impact(self, value: int = 100) -> SimulationInverse: - """Define a value to determine the maximum number of ray impacts during propagation. - - When a ray has interacted N times with the geometry, the propagation of the ray stops. - - Parameters - ---------- - value : int - The maximum number of impacts. - By default, ``100``. - - Returns - ------- - ansys.speos.core.simulation.SimulationInverse - Inverse simulation - """ - self._simulation_template.inverse_mc_simulation_template.max_impact = value - return self def set_weight(self) -> BaseSimulation.Weight: """Activate weight. Highly recommended to fill. @@ -1393,52 +1588,17 @@ def __init__( metadata=metadata, simulation_instance=simulation_instance, ) + self._template_class = "interactive_simulation_template" if default_values: - # Default values - self.set_geom_distance_tolerance() - self.set_max_impact() - self.set_weight() - self.set_colorimetric_standard_CIE_1931() self.set_ambient_material_file_uri() # Default job parameters self.set_light_expert().set_impact_report() - - def set_geom_distance_tolerance(self, value: float = 0.01) -> SimulationInteractive: - """Set the geometry distance tolerance. - - Parameters - ---------- - value : float - Maximum distance in mm to consider two faces as tangent. - By default, ``0.01`` - - Returns - ------- - ansys.speos.core.simulation.SimulationInteractive - Interactive simulation - """ - self._simulation_template.interactive_simulation_template.geom_distance_tolerance = value - return self - - def set_max_impact(self, value: int = 100) -> SimulationInteractive: - """Define a value to determine the maximum number of ray impacts during propagation. - - When a ray has interacted N times with the geometry, the propagation of the ray stops. - - Parameters - ---------- - value : int - The maximum number of impacts. - By default, ``100``. - - Returns - ------- - ansys.speos.core.simulation.SimulationInteractive - Interactive simulation - """ - self._simulation_template.interactive_simulation_template.max_impact = value - return self + # Default values + self.geom_distance_tolerance = 0.01 + self.max_impact = 100 + self.set_weight() + self.set_colorimetric_standard_CIE_1931() def set_weight(self) -> BaseSimulation.Weight: """Activate weight. Highly recommended to fill. @@ -1575,3 +1735,1180 @@ def set_impact_report(self, value: bool = False) -> SimulationInteractive: """ self._job.interactive_simulation_properties.impact_report = value return self + + +class SimulationVirtualBSDF(BaseSimulation): + """Type of simulation : Virtual BSDF Bench. + + By default, + geometry distance tolerance is set to 0.01, + maximum number of impacts is set to 100, + a colorimetric standard is set to CIE 1931, + and weight's minimum energy percentage is set to 0.005. + + Parameters + ---------- + project : ansys.speos.core.project.Project + Project in which simulation shall be created. + name : str + Name of the simulation. + description : str + Description of the Simulation. + By default, ``""``. + metadata : Optional[Mapping[str, str]] + Metadata of the feature. + By default, ``{}``. + simulation_instance : ansys.api.speos.scene.v2.scene_pb2.Scene.SimulationInstance, optional + Simulation instance to provide if the feature does not have to be created from scratch + By default, ``None``, means that the feature is created from scratch by default. + default_values : bool + Uses default values when True. + """ + + class RoughnessOnly(BaseSimulation._SourceSampling): + """Roughness only mode of BSDF bench measurement. + + By default, + 2 degrees uniform type sampling is set + + Parameters + ---------- + mode_template : simulation_template_pb2.RoughnessOnly + roughness settings to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + """ + + def __init__( + self, + mode_template: simulation_template_pb2.RoughnessOnly, + default_values: bool = True, + stable_ctr: bool = False, + ) -> None: + if not stable_ctr: + msg = "RoughnessOnly class instantiated outside of class scope" + raise RuntimeError(msg) + super().__init__( + source_sampling=mode_template, default_values=default_values, stable_ctr=True + ) + + class AllCharacteristics: + """BSDF depends on all properties mode of BSDF bench measurement. + + By default, + is_bsdf180 is true + reflection_and_transmission is true + Color does not depend on viewing direction is set + Source sampling is set to be isotropic + + Parameters + ---------- + mode_template : simulation_template_pb2.AllCharacteristics + all properties dependent BSDF settings to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + """ + + class Iridescence(BaseSimulation._SourceSampling): + """Color depends on viewing direction of BSDF measurement settings. + + By default, + 2 degrees uniform type sampling is set + + Parameters + ---------- + mode_template : simulation_template_pb2.Iridescence + Iridescence settings to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + """ + + def __init__( + self, + iridescence_mode: simulation_template_pb2.Iridescence, + default_values: bool = True, + stable_ctr: bool = False, + ) -> None: + if not stable_ctr: + msg = "AllCharacteristics class instantiated outside of class scope" + raise RuntimeError(msg) + super().__init__( + source_sampling=iridescence_mode, default_values=default_values, stable_ctr=True + ) + + class NonIridescence: + """Color does not depend on viewing direction of BSDF measurement settings. + + By default, + 2 degrees set_isotropic uniform type source sampling is set + + Parameters + ---------- + non_iridescence_mode : simulation_template_pb2.NoIridescence + NonIridescence settings to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + """ + + class Isotropic(BaseSimulation._SourceSampling): + """Uniform Isotropic source sampling. + + By default, + 2 degrees uniform type source sampling is set + + Parameters + ---------- + non_iridescence_isotropic : simulation_template_pb2.Isotropic + Isotropic settings to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + """ + + def __init__( + self, + non_iridescence_isotropic: simulation_template_pb2.Isotropic, + default_values: bool = True, + stable_ctr: bool = False, + ): + if not stable_ctr: + msg = "Isotropic class instantiated outside of class scope" + raise RuntimeError(msg) + super().__init__( + source_sampling=non_iridescence_isotropic, + default_values=default_values, + stable_ctr=True, + ) + + class Anisotropic(BaseSimulation._SourceSampling): + """Anisotropic source sampling. + + Parameters + ---------- + non_iridescence_anisotropic : simulation_template_pb2.Anisotropic + Anisotropic settings to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + """ + + def __init__( + self, + non_iridescence_anisotropic: simulation_template_pb2.Anisotropic, + default_values: bool = True, + stable_ctr: bool = False, + ): + if not stable_ctr: + msg = "Anisotropic class instantiated outside of class scope" + raise RuntimeError(msg) + super().__init__( + source_sampling=non_iridescence_anisotropic, + default_values=default_values, + stable_ctr=True, + ) + + class _Uniform: + """Anisotorpic Uniform sampling mode. + + Parameters + ---------- + uniform : simulation_template_pb2.SourceSamplingUniformAnisotropic to complete. + stable_ctr : bool + Variable to indicate if usage is inside class scope + + """ + + def __init__( + self, + uniform: simulation_template_pb2.SourceSamplingUniformAnisotropic, + default_values: bool = True, + stable_ctr: bool = False, + ) -> None: + if not stable_ctr: + msg = "_Uniform class instantiated outside of class scope" + raise RuntimeError(msg) + self._uniform = uniform + if default_values: + self.theta_sampling = 18 + self.phi_sampling = 36 + self.set_semmetric_none() + + @property + def theta_sampling(self) -> int: + """Get theta_sampling. + + Returns + ------- + int + theta sampling. + + """ + return self._uniform.theta_sampling + + @theta_sampling.setter + def theta_sampling(self, theta_sampling: int) -> None: + """Set theta_sampling. + + Parameters + ---------- + theta_sampling: int + theta sampling. + + Returns + ------- + None + + """ + self._uniform.theta_sampling = theta_sampling + + @property + def phi_sampling(self) -> int: + """Get phi_sampling. + + Returns + ------- + int + phi sampling. + + """ + return self._uniform.phi_sampling + + @phi_sampling.setter + def phi_sampling(self, phi_sampling: int) -> None: + """Set phi_sampling. + + Parameters + ---------- + phi_sampling: int + phi sampling. + + Returns + ------- + None + + """ + self._uniform.phi_sampling = phi_sampling + + def set_symmetric_unspecified( + self, + ) -> ( + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + ): + """Set symmetric type as unspecified. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + + """ + self._uniform.symmetry_type = 0 + return self + + def set_semmetric_none( + self, + ) -> ( + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + ): + """Set symmetric type as non-specified. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + + """ + self._uniform.symmetry_type = 1 + return self + + def set_symmetric_1_plane_symmetric( + self, + ) -> ( + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + ): + """Set symmetric type as plane symmetric. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + + """ + self._uniform.symmetry_type = 2 + return self + + def set_symmetric_2_plane_symmetric( + self, + ) -> ( + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + ): + """Set symmetric type as 2 planes symmetric. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + + """ + self._uniform.symmetry_type = 3 + return self + + def set_uniform( + self, + ) -> SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform: + """Set anisotropic uniform type. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform + + """ + if self._sampling_type is None and self._mode.HasField("uniform_anisotropic"): + _non_iridescence_cls = ( + SimulationVirtualBSDF.AllCharacteristics.NonIridescence + ) # done to pass PEP8 E501 + self._sampling_type = _non_iridescence_cls.Anisotropic._Uniform( + self._mode.uniform_anisotropic, + default_values=False, + stable_ctr=True, + ) + if not isinstance( + self._sampling_type, + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic._Uniform, + ): + _non_iridescence_cls = ( + SimulationVirtualBSDF.AllCharacteristics.NonIridescence + ) # done to pass PEP8 E501 + self._sampling_type = _non_iridescence_cls.Anisotropic._Uniform( + self._mode.uniform_anisotropic, + default_values=True, + stable_ctr=True, + ) + if self._sampling_type._uniform is not self._mode.uniform_anisotropic: + self._sampling_type = self._mode.uniform_anisotropic + return self._sampling_type + + def __init__( + self, + non_iridescence_mode, + default_values: bool = True, + stable_ctr: bool = False, + ): + if not stable_ctr: + msg = "NonIridescence class instantiated outside of class scope" + raise RuntimeError(msg) + self._non_iridescence = non_iridescence_mode + + self._iso_type = None + if default_values: + self._iso_type = self.set_isotropic() + + def set_isotropic( + self, + ) -> SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Isotropic: + """Set isotropic type of uniform source. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Isotropic + Isotropic source settings + + """ + if self._iso_type is None and self._non_iridescence.HasField("isotropic"): + self._iso_type = self.Isotropic( + self._non_iridescence.isotropic, + default_values=False, + stable_ctr=True, + ) + if not isinstance( + self._iso_type, + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Isotropic, + ): + self._iso_type = self.Isotropic( + self._non_iridescence.isotropic, + default_values=True, + stable_ctr=True, + ) + if self._iso_type._mode is not self._non_iridescence.isotropic: + self._iso_type._mode = self._non_iridescence.isotropic + return self._iso_type + + def set_anisotropic( + self, + ) -> SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic: + """Set anisotropic type of uniform source. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.NonIridescence.Anisotropic + Anisotropic source settings + + """ + if self._iso_type is None and self._non_iridescence.HasField("anisotropic"): + self._iso_type = self.Anisotropic( + self._non_iridescence.anisotropic, default_values=False, stable_ctr=True + ) + if not isinstance(self._iso_type, self.Anisotropic): + self._iso_type = self.Anisotropic( + self._non_iridescence.anisotropic, + default_values=True, + stable_ctr=True, + ) + if self._iso_type._mode is not self._non_iridescence.anisotropic: + self._iso_type._mode = self._non_iridescence.anisotropic + return self._iso_type + + def __init__( + self, + mode_template: simulation_template_pb2.VirtualBSDFBench, + default_values: bool = True, + stable_ctr: bool = False, + ) -> None: + if not stable_ctr: + msg = "AllCharacteristics class instantiated outside of class scope" + raise RuntimeError(msg) + self._all_characteristics_mode = mode_template + + self._iridescence_mode = None + self._iridescence_mode = self.set_non_iridescence() + + # Default values + if default_values: + self.is_bsdf180 = False + self.reflection_and_transmission = False + + @property + def is_bsdf180(self) -> bool: + """Get settings if bsdf is bsdf180. + + Returns + ------- + bool + True if bsdf180 is to be generated, False otherwise. + + """ + return self._all_characteristics_mode.is_bsdf180 + + @is_bsdf180.setter + def is_bsdf180(self, value: bool) -> None: + """Set settings if bsdf180. + + Parameters + ---------- + value: bool + True if bsdf180 is to be generated, False otherwise. + + Returns + ------- + None + + """ + self._all_characteristics_mode.is_bsdf180 = value + + @property + def reflection_and_transmission(self) -> bool: + """Get settings if reflection and transmission is to be generated. + + Returns + ------- + bool + True if reflection and transmission is to be generated, False otherwise. + + """ + return self._all_characteristics_mode.sensor_reflection_and_transmission + + @reflection_and_transmission.setter + def reflection_and_transmission(self, value: bool) -> None: + """Set settings if reflection and transmission is to be generated. + + Parameters + ---------- + value: bool + True if reflection and transmission is to be generated, False otherwise. + + Returns + ------- + None + + """ + self._all_characteristics_mode.sensor_reflection_and_transmission = value + + def set_non_iridescence(self) -> SimulationVirtualBSDF.AllCharacteristics.NonIridescence: + """Set bsdf color does not depend on viewing direction. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.NonIridescence + NonIridescence settings to be complete + """ + if self._iridescence_mode is None and self._all_characteristics_mode.HasField( + "no_iridescence" + ): + self._iridescence_mode = self.NonIridescence( + non_iridescence_mode=self._all_characteristics_mode.no_iridescence, + default_values=False, + stable_ctr=True, + ) + if not isinstance( + self._iridescence_mode, SimulationVirtualBSDF.AllCharacteristics.NonIridescence + ): + self._iridescence_mode = self.NonIridescence( + non_iridescence_mode=self._all_characteristics_mode.no_iridescence, + default_values=True, + stable_ctr=True, + ) + if ( + self._iridescence_mode._non_iridescence + is not self._all_characteristics_mode.no_iridescence + ): + self._iridescence_mode._non_iridescence = ( + self._all_characteristics_mode.no_iridescence + ) + return self._iridescence_mode + + def set_iridescence(self) -> SimulationVirtualBSDF.AllCharacteristics.Iridescence: + """Set bsdf color depends on viewing direction. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics.Iridescence + Iridescence settings to be complete + """ + if self._iridescence_mode is None and self._all_characteristics_mode.HasField( + "iridescence" + ): + self._iridescence_mode = self.Iridescence( + iridescence_mode=self._all_characteristics_mode.iridescence, + default_values=False, + stable_ctr=True, + ) + if not isinstance( + self._iridescence_mode, SimulationVirtualBSDF.AllCharacteristics.Iridescence + ): + self._iridescence_mode = self.Iridescence( + iridescence_mode=self._all_characteristics_mode.iridescence, + default_values=True, + stable_ctr=True, + ) + if self._iridescence_mode._mode is not self._all_characteristics_mode.iridescence: + self._iridescence_mode._mode = self._all_characteristics_mode.iridescence + return self._iridescence_mode + + class WavelengthsRange: + """Range of wavelengths. + + By default, a range from 400nm to 700nm is chosen, with a sampling of 13. + + Parameters + ---------- + wavelengths_range : ansys.api.speos.sensor.v1.common_pb2.WavelengthsRange + Wavelengths range protobuf object to modify. + default_values : bool + Uses default values when True. + stable_ctr : bool + Variable to indicate if usage is inside class scope + + Notes + ----- + **Do not instantiate this class yourself**, use set_wavelengths_range method available in + sensor classes. + """ + + def __init__( + self, + wavelengths_range, + default_values: bool = True, + stable_ctr: bool = False, + ) -> None: + if not stable_ctr: + msg = "WavelengthsRange class instantiated outside of class scope" + raise RuntimeError(msg) + self._wavelengths_range = wavelengths_range + + if default_values: + # Default values + self.start = 400 + self.end = 700 + self.sampling = 13 + + @property + def start(self) -> float: + """Return start wavelength. + + Returns + ------- + float + Start wavelength. + + """ + return self._wavelengths_range.w_start + + @start.setter + def start(self, value: float) -> None: + """Set start wavelength. + + Parameters + ---------- + value: float + Start wavelength. + + Returns + ------- + None + """ + self._wavelengths_range.w_start = value + + @property + def end(self) -> float: + """ + Return end wavelength. + + Returns + ------- + float + End wavelength. + + """ + return self._wavelengths_range.w_end + + @end.setter + def end(self, value: float) -> None: + """ + Set end wavelength. + + Parameters + ---------- + value: float + End wavelength. + + Returns + ------- + None + + """ + self._wavelengths_range.w_end = value + + @property + def sampling(self) -> int: + """ + Return sampling. + + Returns + ------- + int + Wavelength sampling. + + """ + return self._wavelengths_range.w_sampling + + @sampling.setter + def sampling(self, value: int) -> None: + """ + Set sampling. + + Parameters + ---------- + value: int + wavelength sampling. + + Returns + ------- + None + + """ + self._wavelengths_range.w_sampling = value + + class SensorUniform: + """BSDF bench sensor settings.""" + + def __init__( + self, + sensor_uniform_mode, + default_values: bool = True, + stable_ctr: bool = False, + ): + if not stable_ctr: + msg = "SensorUniform class instantiated outside of class scope" + raise RuntimeError(msg) + self._sensor_uniform_mode = sensor_uniform_mode + if default_values: + self.theta_sampling = 45 + self.phi_sampling = 180 + + @property + def theta_sampling(self) -> int: + """Get theta sampling. + + Returns + ------- + int + theta sampling. + + """ + return self._sensor_uniform_mode.theta_sampling + + @theta_sampling.setter + def theta_sampling(self, value: int) -> None: + """ + Set theta sampling. + + Parameters + ---------- + value: int + theta sampling. + + Returns + ------- + None + + """ + self._sensor_uniform_mode.theta_sampling = value + + @property + def phi_sampling(self) -> int: + """ + Get phi sampling. + + Returns + ------- + int + phi sampling. + + """ + return self._sensor_uniform_mode.phi_sampling + + @phi_sampling.setter + def phi_sampling(self, value: int) -> None: + """ + Set phi sampling. + + Parameters + ---------- + value: int + phi sampling. + + Returns + ------- + None + + """ + self._sensor_uniform_mode.phi_sampling = value + + def __init__( + self, + project: project.Project, + name: str, + description: str = "", + metadata: Optional[Mapping[str, str]] = None, + simulation_instance: Optional[ProtoScene.SimulationInstance] = None, + default_values: bool = True, + ) -> None: + if metadata is None: + metadata = {} + + super().__init__( + project=project, + name=name, + description=description, + metadata=metadata, + simulation_instance=simulation_instance, + ) + self._template_class = "virtual_bsdf_bench_simulation_template" + + self._wavelengths_range = None + self._sensor_sampling_mode = None + self._mode = None + + self._wavelengths_range = self.set_wavelengths_range() + self._sensor_sampling_mode = self.set_sensor_sampling_uniform() + self._mode = self.set_mode_all_characteristics() + + if default_values: + self.analysis_x_ratio = 100 + self.analysis_y_ratio = 100 + self.axis_system = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1] + self.integration_angle = 2 + self.stop_condition_ray_number = 100000 + # default simulation properties + self.geom_distance_tolerance = 0.01 + self.max_impact = 100 + self.set_weight() + self.set_colorimetric_standard_CIE_1931() + + @property + def integration_angle(self) -> float: + """Return the sensor integration angle. + + Returns + ------- + float + The sensor integration angle. + + """ + tmp_sensor = self._simulation_template.virtual_bsdf_bench_simulation_template.sensor + return tmp_sensor.integration_angle + + @integration_angle.setter + def integration_angle(self, angle: float) -> None: + """Set the sensor integration angle. + + Parameters + ---------- + angle: float + The sensor integration angle. + + Returns + ------- + None + + """ + tmp_sensor = self._simulation_template.virtual_bsdf_bench_simulation_template.sensor + tmp_sensor.integration_angle = angle + + @property + def axis_system(self) -> List[float]: + """Get axis system of the bsdf bench. + + Returns + ------- + List[float] + The axis system of the bsdf bench. + + """ + return self._simulation_instance.vbb_properties.axis_system + + @axis_system.setter + def axis_system(self, value: List[float]) -> None: + """Set axis system of the bsdf bench. + + Parameters + ---------- + value: List[float] + The axis system of the bsdf bench. + + Returns + ------- + None + + """ + self._simulation_instance.vbb_properties.axis_system[:] = value + + @property + def analysis_x_ratio(self) -> float: + """Get analysis x ratio, value must be in range [0., 100.]. + + Returns + ------- + float + Ratio to reduce the analysis area following x + + """ + return self._simulation_instance.vbb_properties.analysis_x_ratio + + @analysis_x_ratio.setter + def analysis_x_ratio(self, value: float) -> None: + """Set analysis x ratio, value must be in range [0., 100.]. + + Parameters + ---------- + value: float + The analysis x ratio in range [0., 100.] + + Returns + ------- + None + + """ + self._simulation_instance.vbb_properties.analysis_x_ratio = value + + @property + def analysis_y_ratio(self) -> float: + """Get analysis y ratio, value must be in range [0., 100.]. + + Returns + ------- + float + Ratio to reduce the analysis area following y + + """ + return self._simulation_instance.vbb_properties.analysis_y_ratio + + @analysis_y_ratio.setter + def analysis_y_ratio(self, value: float) -> None: + """Set analysis y ratio, value must be in range [0., 100.]. + + Parameters + ---------- + value: float + The analysis y ratio in range [0., 100.] + + Returns + ------- + None + + """ + self._simulation_instance.vbb_properties.analysis_y_ratio = value + + @property + def stop_condition_ray_number(self) -> int: + """Get ray stop condition ray number. + + Returns + ------- + float + The ray stop condition ray number. + + """ + return self._job.virtualbsdfbench_simulation_properties.stop_condition_rays_number + + @stop_condition_ray_number.setter + def stop_condition_ray_number(self, value: int) -> None: + """Set ray stop condition ray number. + + Parameters + ---------- + value: int + The ray stop condition ray number. + + Returns + ------- + None + + """ + self._job.virtualbsdfbench_simulation_properties.stop_condition_rays_number = value + + def set_sensor_paths(self, sensor_paths: List[str]) -> None: + """ + Disable setting sensor paths for this subclass. + + This method is intentionally not supported in ``SimulationVirtualBSDF``. + It exists only to satisfy the interface defined in the base class and + will always raise a ``NotImplementedError`` when called. + + Parameters + ---------- + sensor_paths : list of str + Ignored. Present only for compatibility with the base class. + + Returns + ------- + None + This method does not return anything. It always raises an exception. + + Raises + ------ + NotImplementedError + Always raised, since this method is disabled in this subclass. + """ + raise NotImplementedError("This method is disabled in SimulationVirtualBSDF") + + def set_source_paths(self, source_paths: List[str]) -> None: + """ + Disable setting source paths for this subclass. + + This method is intentionally not supported in ``SimulationVirtualBSDF``. + It exists only to satisfy the interface defined in the base class and + will always raise a ``NotImplementedError`` when called. + + Parameters + ---------- + source_paths : list of str + Ignored. Present only for compatibility with the base class. + + Returns + ------- + None + This method does not return anything. It always raises an exception. + + Raises + ------ + NotImplementedError + Always raised, since this method is disabled in this subclass. + """ + raise NotImplementedError("This method is disabled in SimulationVirtualBSDF") + + def set_weight(self) -> BaseSimulation.Weight: + """Activate weight. Highly recommended to fill. + + Returns + ------- + ansys.speos.core.simulation.BaseSimulation.Weight + Simulation.Weight + """ + return BaseSimulation.Weight( + self._simulation_template.virtual_bsdf_bench_simulation_template.weight, + stable_ctr=True, + ) + + def set_weight_none(self) -> SimulationVirtualBSDF: + """Deactivate weight. + + Returns + ------- + ansys.speos.core.simulation.SimulationVirtualBSDF + Inverse simulation + """ + self._simulation_template.virtual_bsdf_bench_simulation_template.ClearField("weight") + return self + + def set_colorimetric_standard_CIE_1931(self) -> SimulationVirtualBSDF: + """Set the colorimetric standard to CIE 1931. + + 2 degrees CIE Standard Colorimetric Observer Data. + + Returns + ------- + ansys.speos.core.simulation.SimulationVirtualBSDF + Inverse simulation + """ + self._simulation_template.virtual_bsdf_bench_simulation_template.colorimetric_standard = ( + simulation_template_pb2.CIE_1931 + ) + return self + + def set_colorimetric_standard_CIE_1964(self) -> SimulationVirtualBSDF: + """Set the colorimetric standard to CIE 1964. + + 10 degrees CIE Standard Colorimetric Observer Data. + + Returns + ------- + ansys.speos.core.simulation.SimulationVirtualBSDF + Inverse simulation + """ + self._simulation_template.virtual_bsdf_bench_simulation_template.colorimetric_standard = ( + simulation_template_pb2.CIE_1964 + ) + return self + + def set_mode_roughness_only(self) -> SimulationVirtualBSDF.RoughnessOnly: + """Set BSDF depends on surface roughness only. + + Returns + ------- + SimulationVirtualBSDF.RoughnessOnly + roughness only settings + """ + if ( + self._mode is None + and self._simulation_template.virtual_bsdf_bench_simulation_template.HasField( + "roughness_only" + ) + ): + self._mode = SimulationVirtualBSDF.RoughnessOnly( + self._simulation_template.virtual_bsdf_bench_simulation_template.roughness_only, + default_values=False, + stable_ctr=True, + ) + if not isinstance(self._mode, SimulationVirtualBSDF.RoughnessOnly): + self._mode = SimulationVirtualBSDF.RoughnessOnly( + self._simulation_template.virtual_bsdf_bench_simulation_template.roughness_only, + default_values=True, + stable_ctr=True, + ) + if ( + self._mode._mode + is not self._simulation_template.virtual_bsdf_bench_simulation_template.roughness_only + ): + self._mode._mode = ( + self._simulation_template.virtual_bsdf_bench_simulation_template.roughness_only + ) + return self._mode + + def set_mode_all_characteristics(self) -> SimulationVirtualBSDF.AllCharacteristics: + """Set BSDF depends on all properties. + + Returns + ------- + SimulationVirtualBSDF.AllCharacteristics + all properties settings + """ + if ( + self._mode is None + and self._simulation_template.virtual_bsdf_bench_simulation_template.HasField( + "all_characteristics" + ) + ): + self._mode = SimulationVirtualBSDF.AllCharacteristics( + self._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics, + default_values=False, + stable_ctr=True, + ) + elif not isinstance(self._mode, SimulationVirtualBSDF.AllCharacteristics): + # if the _type is not Colorimetric then we create a new type. + self._mode = SimulationVirtualBSDF.AllCharacteristics( + self._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics, + default_values=True, + stable_ctr=True, + ) + if self._mode._all_characteristics_mode is not ( + self._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ): + self._mode._all_characteristics_mode = ( + self._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + return self._mode + + def set_wavelengths_range(self) -> SimulationVirtualBSDF.WavelengthsRange: + """Set the range of wavelengths. + + Returns + ------- + ansys.speos.core.sensor.BaseSensor.WavelengthsRange + Wavelengths range. + """ + if self._wavelengths_range is None: + return SimulationVirtualBSDF.WavelengthsRange( + wavelengths_range=self._simulation_template.virtual_bsdf_bench_simulation_template.wavelengths_range, + default_values=True, + stable_ctr=True, + ) + if self._wavelengths_range._wavelengths_range is not ( + self._simulation_template.virtual_bsdf_bench_simulation_template.wavelengths_range + ): + # Happens in case of feature reset (to be sure to always modify correct data) + self._wavelengths_range._wavelengths_range = ( + self._simulation_template.virtual_bsdf_bench_simulation_template.wavelengths_range + ) + return self._wavelengths_range + + def set_sensor_sampling_uniform(self) -> SimulationVirtualBSDF.SensorUniform: + """Set sensor sampling uniform. + + Returns + ------- + ansys.speos.core.sensor.BaseSensor.SensorUniform + uniform type of sensor settings + + """ + if ( + self._sensor_sampling_mode is None + and self._simulation_template.virtual_bsdf_bench_simulation_template.sensor.HasField( + "uniform" + ) + ): + self._sensor_sampling_mode = SimulationVirtualBSDF.SensorUniform( + self._simulation_template.virtual_bsdf_bench_simulation_template.sensor.uniform, + default_values=False, + stable_ctr=True, + ) + if not isinstance(self._sensor_sampling_mode, SimulationVirtualBSDF.SensorUniform): + self._sensor_sampling_mode = SimulationVirtualBSDF.SensorUniform( + self._simulation_template.virtual_bsdf_bench_simulation_template.sensor.uniform, + default_values=True, + stable_ctr=True, + ) + if ( + self._sensor_sampling_mode._sensor_uniform_mode + is not self._simulation_template.virtual_bsdf_bench_simulation_template.sensor.uniform + ): + # Happens in case of feature reset (to be sure to always modify correct data) + self._sensor_sampling_mode._sensor_uniform_mode = ( + self._simulation_template.virtual_bsdf_bench_simulation_template.sensor.uniform + ) + return self._sensor_sampling_mode + + def set_sensor_sampling_automatic(self) -> SimulationVirtualBSDF: + """Set sensor sampling automatic. + + Returns + ------- + SimulationVirtualBSDF + + """ + self._simulation_template.virtual_bsdf_bench_simulation_template.sensor.automatic.SetInParent() + return self diff --git a/tests/assets/nx_vbb_export.speos/nx_vbb_export.speos b/tests/assets/nx_vbb_export.speos/nx_vbb_export.speos new file mode 100644 index 000000000..cc008bb4f Binary files /dev/null and b/tests/assets/nx_vbb_export.speos/nx_vbb_export.speos differ diff --git a/tests/core/test_simulation.py b/tests/core/test_simulation.py index 775233c81..0ab0a96a3 100644 --- a/tests/core/test_simulation.py +++ b/tests/core/test_simulation.py @@ -33,6 +33,7 @@ SimulationDirect, SimulationInteractive, SimulationInverse, + SimulationVirtualBSDF, ) from ansys.speos.core.source import SourceLuminaire from tests.conftest import config, test_path @@ -71,11 +72,11 @@ def test_create_direct(speos: Speos): # Change value # geom_distance_tolerance - sim1.set_geom_distance_tolerance(value=0.1) + sim1.geom_distance_tolerance = 0.1 assert simulation_template.geom_distance_tolerance == 0.1 # max_impact - sim1.set_max_impact(value=200) + sim1.max_impact = 200 assert simulation_template.max_impact == 200 # weight - minimum_energy_percentage @@ -172,11 +173,11 @@ def test_create_inverse(speos: Speos): # Change value # geom_distance_tolerance - sim1.set_geom_distance_tolerance(value=0.1) + sim1.geom_distance_tolerance = 0.1 assert simulation_template.geom_distance_tolerance == 0.1 # max_impact - sim1.set_max_impact(value=200) + sim1.max_impact = 200 assert simulation_template.max_impact == 200 # weight - minimum_energy_percentage @@ -287,11 +288,11 @@ def test_create_interactive(speos: Speos): # Change value # geom_distance_tolerance - sim1.set_geom_distance_tolerance(value=0.1) + sim1.geom_distance_tolerance = 0.1 assert sim1._simulation_template.interactive_simulation_template.geom_distance_tolerance == 0.1 # max_impact - sim1.set_max_impact(value=200) + sim1.max_impact = 200 assert sim1._simulation_template.interactive_simulation_template.max_impact == 200 # weight - minimum_energy_percentage @@ -369,6 +370,274 @@ def test_create_interactive(speos: Speos): sim1.delete() +@pytest.mark.supported_speos_versions(min=252) +def test_create_virtual_bsdf_bench(speos: Speos): + """Test creation of Virtual BSDF Bench Simulation.""" + p = Project(speos=speos) + vbb = p.create_simulation("virtual_bsdf_bench_1", feature_type=SimulationVirtualBSDF) + + # Check default properties + # Check backend property + assert vbb._simulation_template.HasField("virtual_bsdf_bench_simulation_template") + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.geom_distance_tolerance + == 0.01 + ) + assert vbb._simulation_template.virtual_bsdf_bench_simulation_template.max_impact == 100 + assert vbb._simulation_template.virtual_bsdf_bench_simulation_template.HasField("weight") + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.weight.minimum_energy_percentage + == 0.005 + ) + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.colorimetric_standard + is simulation_template_pb2.CIE_1931 + ) + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.wavelengths_range.w_start + == 400 + ) + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.wavelengths_range.w_end + == 700 + ) + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.wavelengths_range.w_sampling + == 13 + ) + assert vbb._simulation_instance.vbb_properties.axis_system[:] == [ + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + ] + assert vbb._simulation_instance.vbb_properties.analysis_x_ratio == 100 + assert vbb._simulation_instance.vbb_properties.analysis_y_ratio == 100 + + # Check mode and source settings + assert vbb._simulation_template.virtual_bsdf_bench_simulation_template.HasField( + "all_characteristics" + ) + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + assert backend_properties.is_bsdf180 is False + assert backend_properties.sensor_reflection_and_transmission is False + assert backend_properties.HasField("no_iridescence") + assert backend_properties.no_iridescence.HasField("isotropic") + assert backend_properties.no_iridescence.isotropic.HasField("uniform_isotropic") + assert backend_properties.no_iridescence.isotropic.uniform_isotropic.theta_sampling == 18 + + # Check sensor settings + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.sensor.integration_angle + == 2 + ) + assert vbb._simulation_template.virtual_bsdf_bench_simulation_template.sensor.HasField( + "uniform" + ) + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.sensor.uniform.theta_sampling + == 45 + ) + assert ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.sensor.uniform.phi_sampling + == 180 + ) + + # Check frontend property + assert vbb.geom_distance_tolerance == 0.01 + assert vbb.max_impact == 100 + assert vbb.integration_angle == 2 + assert vbb.axis_system == [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1] + assert vbb.analysis_x_ratio == 100 + assert vbb.analysis_y_ratio == 100 + assert vbb.set_wavelengths_range().start == 400 + assert vbb.set_wavelengths_range().end == 700 + assert vbb.set_wavelengths_range().sampling == 13 + assert vbb.set_mode_all_characteristics().is_bsdf180 is False + assert vbb.set_mode_all_characteristics().reflection_and_transmission is False + assert ( + vbb.set_mode_all_characteristics() + .set_non_iridescence() + .set_isotropic() + .set_uniform() + .theta_sampling + == 18 + ) + assert vbb.set_sensor_sampling_uniform().theta_sampling == 45 + assert vbb.set_sensor_sampling_uniform().phi_sampling == 180 + + # Change isotropic adaptive source sampling + vbb.set_mode_all_characteristics().set_non_iridescence().set_isotropic().set_adaptive() + # Check backend properties + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + assert backend_properties.no_iridescence.isotropic.HasField("adaptive") + assert backend_properties.no_iridescence.isotropic.adaptive.file_uri == "" + # Check frontend properties + assert ( + vbb.set_mode_all_characteristics() + .set_non_iridescence() + .set_isotropic() + .set_adaptive() + .adaptive_uri + == "" + ) + + # Check if properties are saved + vbb.set_mode_all_characteristics().set_non_iridescence().set_isotropic() + # Check backend properties + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + assert backend_properties.no_iridescence.isotropic.HasField("adaptive") + assert backend_properties.no_iridescence.isotropic.adaptive.file_uri == "" + # Check frontend properties + assert ( + vbb.set_mode_all_characteristics() + .set_non_iridescence() + .set_isotropic() + .set_adaptive() + .adaptive_uri + == "" + ) + + # Change back to isotropic uniform source sampling + vbb.set_mode_all_characteristics().set_non_iridescence().set_isotropic().set_uniform() + # Check backend properties + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + assert backend_properties.no_iridescence.isotropic.HasField("uniform_isotropic") + # Check frontend properties + assert backend_properties.no_iridescence.isotropic.uniform_isotropic.theta_sampling == 18 + + # Change anisotropic uniform + vbb.set_mode_all_characteristics().set_non_iridescence().set_anisotropic() + # Check backend properties + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + assert backend_properties.no_iridescence.HasField("anisotropic") + assert backend_properties.no_iridescence.anisotropic.HasField("uniform_anisotropic") + assert backend_properties.no_iridescence.anisotropic.uniform_anisotropic.theta_sampling == 18 + assert backend_properties.no_iridescence.anisotropic.uniform_anisotropic.phi_sampling == 36 + assert backend_properties.no_iridescence.anisotropic.uniform_anisotropic.symmetry_type == 1 + # Check frontend properties + assert ( + vbb.set_mode_all_characteristics() + .set_non_iridescence() + .set_anisotropic() + .set_uniform() + .theta_sampling + == 18 + ) + assert ( + vbb.set_mode_all_characteristics() + .set_non_iridescence() + .set_anisotropic() + .set_uniform() + .phi_sampling + == 36 + ) + + # Change anisotropic adaptive + vbb.set_mode_all_characteristics().set_non_iridescence().set_anisotropic().set_adaptive() + # Check backend properties + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + assert backend_properties.no_iridescence.anisotropic.HasField("adaptive") + assert backend_properties.no_iridescence.anisotropic.adaptive.file_uri == "" + # Check frontend properties + assert ( + vbb.set_mode_all_characteristics() + .set_non_iridescence() + .set_anisotropic() + .set_adaptive() + .adaptive_uri + == "" + ) + + # Change color depending on viewing angle + vbb.set_mode_all_characteristics().set_iridescence() + # Check backend properties + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + assert backend_properties.HasField("iridescence") + assert backend_properties.iridescence.HasField("uniform_isotropic") + assert backend_properties.iridescence.uniform_isotropic.theta_sampling == 18 + # Check frontend properties + assert vbb.set_mode_all_characteristics().set_iridescence().set_uniform().theta_sampling == 18 + + # Change color depending on viewing angle with adaptive source sampling + vbb.set_mode_all_characteristics().set_iridescence().set_adaptive() + # Check backend properties + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.all_characteristics + ) + assert backend_properties.iridescence.HasField("adaptive") + assert backend_properties.iridescence.adaptive.file_uri == "" + # Check frontend properties + assert vbb.set_mode_all_characteristics().set_iridescence().set_adaptive().adaptive_uri == "" + + # Change mode to surface roughness only + vbb.set_mode_roughness_only() + # Check backend property + assert not vbb._simulation_template.virtual_bsdf_bench_simulation_template.HasField( + "all_characteristics" + ) + assert vbb._simulation_template.virtual_bsdf_bench_simulation_template.HasField( + "roughness_only" + ) + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.roughness_only + ) + assert backend_properties.HasField("uniform_isotropic") + assert backend_properties.uniform_isotropic.theta_sampling == 18 + # Check frontend properties + assert vbb.set_mode_roughness_only().set_uniform().theta_sampling == 18 + + # Change to adaptive source sampling + vbb.set_mode_roughness_only().set_adaptive() + # Check backend properties + backend_properties = ( + vbb._simulation_template.virtual_bsdf_bench_simulation_template.roughness_only + ) + assert backend_properties.HasField("adaptive") + assert backend_properties.adaptive.file_uri == "" + # Check frontend properties + assert vbb.set_mode_roughness_only().set_adaptive().adaptive_uri == "" + + # Change sensor to automatic + vbb.set_sensor_sampling_automatic() + assert vbb._simulation_template.virtual_bsdf_bench_simulation_template.sensor.HasField( + "automatic" + ) + vbb.delete() + + +def test_load_virtual_bsdf_bench(speos: Speos): + """Test load of a exported virtual bsdf bench simulation.""" + p = Project( + speos=speos, path=str(Path(test_path) / "nx_vbb_export.speos" / "nx_vbb_export.speos") + ) + assert p is not None + sims = p.find(name=".*", name_regex=True, feature_type=SimulationVirtualBSDF) + assert len(sims) > 0 + + def test_commit(speos: Speos): """Test commit of simulation.""" p = Project(speos=speos) @@ -415,7 +684,7 @@ def test_commit(speos: Speos): assert p.scene_link.get().simulations[0] == sim1._simulation_instance # Change only in local not committed (on template, on instance) - sim1.set_geom_distance_tolerance(value=0.1) + sim1.geom_distance_tolerance = 0.1 assert sim1.simulation_template_link.get() != sim1._simulation_template sim1.set_sensor_paths(["Irradiance.1, Irradiance.2"]) assert p.scene_link.get().simulations[0] != sim1._simulation_instance @@ -464,7 +733,7 @@ def test_reset(speos: Speos): assert sim1._job.HasField("direct_mc_simulation_properties") # local # Change local data (on template, on instance) - sim1.set_geom_distance_tolerance(value=0.1) + sim1.geom_distance_tolerance = 0.1 assert sim1.simulation_template_link.get() != sim1._simulation_template sim1.set_sensor_paths(["Irradiance.1, Irradiance.2"]) assert p.scene_link.get().simulations[0] != sim1._simulation_instance @@ -532,7 +801,7 @@ def test_direct_modify_after_reset(speos: Speos): # Modify after a reset # Template assert sim1._simulation_template.direct_mc_simulation_template.geom_distance_tolerance == 0.01 - sim1.set_geom_distance_tolerance(value=0.05) + sim1.geom_distance_tolerance = 0.05 assert sim1._simulation_template.direct_mc_simulation_template.geom_distance_tolerance == 0.05 # Props @@ -603,7 +872,7 @@ def test_inverse_modify_after_reset(speos: Speos): # Modify after a reset # Template assert sim1._simulation_template.inverse_mc_simulation_template.geom_distance_tolerance == 0.01 - sim1.set_geom_distance_tolerance(value=0.05) + sim1.geom_distance_tolerance = 0.05 assert sim1._simulation_template.inverse_mc_simulation_template.geom_distance_tolerance == 0.05 # Props @@ -666,7 +935,7 @@ def test_interactive_modify_after_reset(speos: Speos): # Modify after a reset # Template assert sim1._simulation_template.interactive_simulation_template.geom_distance_tolerance == 0.01 - sim1.set_geom_distance_tolerance(value=0.05) + sim1.geom_distance_tolerance = 0.05 assert sim1._simulation_template.interactive_simulation_template.geom_distance_tolerance == 0.05 # Props