diff --git a/include/gz/sim/Light.hh b/include/gz/sim/Light.hh index 13cad9c3be..e34ca6487e 100644 --- a/include/gz/sim/Light.hh +++ b/include/gz/sim/Light.hh @@ -232,7 +232,7 @@ namespace gz /// \brief Set attenuation range of this light. /// \param[in] _ecm Entity-component manager. /// Light attenuation is not applicable to directional lights. - /// \param[in] _bool True to cast shadows, false to not cast shadows. + /// \param[in] _range Attenuation range value to set. public: void SetAttenuationRange(EntityComponentManager &_ecm, double _range); diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 59c82b23bc..25c976c439 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -46,6 +46,7 @@ pybind11_add_module(${BINDINGS_MODULE_NAME} MODULE src/gz/sim/EntityComponentManager.cc src/gz/sim/EventManager.cc src/gz/sim/Joint.cc + src/gz/sim/Light.cc src/gz/sim/Link.cc src/gz/sim/Model.cc src/gz/sim/TestFixture.cc @@ -98,6 +99,7 @@ if (BUILD_TESTING) set(python_tests actor_TEST joint_TEST + light_TEST link_TEST model_TEST sensor_TEST diff --git a/python/src/gz/sim/Light.cc b/python/src/gz/sim/Light.cc new file mode 100644 index 0000000000..5b3eb4792a --- /dev/null +++ b/python/src/gz/sim/Light.cc @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2023 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include + +#include "Light.hh" + +namespace py = pybind11; + +namespace gz +{ +namespace sim +{ +namespace python +{ +void defineSimLight(py::object module) +{ + py::class_(module, "Light") + .def(py::init()) + .def(py::init()) + .def("entity", &gz::sim::Light::Entity, + "Get the entity which this light is related to.") + .def("reset_entity", &gz::sim::Light::ResetEntity, + "Reset Entity to a new one.") + .def("valid", &gz::sim::Light::Valid, + py::arg("ecm"), + "Check whether this light correctly refers to an entity that " + "has a components::Light.") + .def("name", &gz::sim::Light::Name, + py::arg("ecm"), + "Get the light's unscoped name.") + .def("pose", &gz::sim::Light::Pose, + py::arg("ecm"), + "Get the pose of the light. " + "The pose is given w.r.t the light's parent. which can be a world or " + "a link.") + .def("type", &gz::sim::Light::Type, + py::arg("ecm"), + "Get the light type.") + .def("diffuse_color", &gz::sim::Light::DiffuseColor, + py::arg("ecm"), + "Get the light diffuse color.") + .def("specular_color", &gz::sim::Light::SpecularColor, + py::arg("ecm"), + "Get the light specular color.") + .def("cast_shadows", &gz::sim::Light::CastShadows, + py::arg("ecm"), + "Get whether the light casts shadows.") + .def("intensity", &gz::sim::Light::Intensity, + py::arg("ecm"), + "Get the light intensity.") + .def("direction", &gz::sim::Light::Direction, + py::arg("ecm"), + "Get the light direction.") + .def("attenuation_range", &gz::sim::Light::AttenuationRange, + py::arg("ecm"), + "Get the light attenuation range. " + "Light attenuation is not applicable to directional lights.") + .def("attenuation_constant", &gz::sim::Light::AttenuationConstant, + py::arg("ecm"), + "Get the light attenuation constant value. " + "Light attenuation is not applicable to directional lights.") + .def("attenuation_linear", &gz::sim::Light::AttenuationLinear, + py::arg("ecm"), + "Get the light attenuation linear value. " + "Light attenuation is not applicable to directional lights.") + .def("attenuation_quadratic", &gz::sim::Light::AttenuationQuadratic, + py::arg("ecm"), + "Get the light attenuation quadratic value. " + "Light attenuation is not applicable to directional lights.") + .def("spot_inner_angle", &gz::sim::Light::SpotInnerAngle, + py::arg("ecm"), + "Get the inner angle of light. Applies to spot lights only.") + .def("spot_outer_angle", &gz::sim::Light::SpotOuterAngle, + py::arg("ecm"), + "Get the outer angle of light. Applies to spot lights only.") + .def("spot_falloff", &gz::sim::Light::SpotFalloff, + py::arg("ecm"), + "Get the fall off value of light. Applies to spot lights only.") + .def("set_pose", &gz::sim::Light::SetPose, + py::arg("ecm"), + py::arg("pose"), + "Set the pose of this light.") + .def("set_diffuse_color", &gz::sim::Light::SetDiffuseColor, + py::arg("ecm"), + py::arg("color"), + "Set the diffuse color of this light.") + .def("set_specular_color", &gz::sim::Light::SetSpecularColor, + py::arg("ecm"), + py::arg("color"), + "Set the specular color of this light.") + .def("set_cast_shadows", &gz::sim::Light::SetCastShadows, + py::arg("ecm"), + py::arg("cast_shadows"), + "Set whether the light casts shadows.") + .def("set_intensity", &gz::sim::Light::SetIntensity, + py::arg("ecm"), + py::arg("value"), + "Set light intensity.") + .def("set_direction", &gz::sim::Light::SetDirection, + py::arg("ecm"), + py::arg("direction"), + "Set light direction. Applies to directional lights.") + .def("set_attenuation_range", &gz::sim::Light::SetAttenuationRange, + py::arg("ecm"), + py::arg("range"), + "Set attenuation range of this light.") + .def("set_attenuation_constant", &gz::sim::Light::SetAttenuationConstant, + py::arg("ecm"), + py::arg("value"), + "Set attenuation constant value of this light.") + .def("set_attenuation_linear", &gz::sim::Light::SetAttenuationLinear, + py::arg("ecm"), + py::arg("value"), + "Set attenuation linear value of this light.") + .def("set_attenuation_quadratic", &gz::sim::Light::SetAttenuationQuadratic, + py::arg("ecm"), + py::arg("color"), + "Set attenuation quadratic value of this light.") + .def("set_spot_inner_angle", &gz::sim::Light::SetSpotInnerAngle, + py::arg("ecm"), + py::arg("angle"), + "Set inner angle for this light. Applies to spot lights only.") + .def("set_spot_outer_angle", &gz::sim::Light::SetSpotOuterAngle, + py::arg("ecm"), + py::arg("angle"), + "Set outer angle for this light. Applies to spot lights only.") + .def("set_spot_falloff", &gz::sim::Light::SetSpotFalloff, + py::arg("ecm"), + py::arg("falloff"), + "Set fall off value for this light. Applies to spot lights only.") + .def("parent", &gz::sim::Light::Parent, + py::arg("ecm"), + "Get the parent entity. This can be a world or a link.") + .def("__copy__", + [](const gz::sim::Light &self) + { + return gz::sim::Light(self); + }) + .def("__deepcopy__", + [](const gz::sim::Light &self, pybind11::dict) + { + return gz::sim::Light(self); + }); +} +} // namespace python +} // namespace sim +} // namespace gz diff --git a/python/src/gz/sim/Light.hh b/python/src/gz/sim/Light.hh new file mode 100644 index 0000000000..1daea7e789 --- /dev/null +++ b/python/src/gz/sim/Light.hh @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GZ_SIM_PYTHON__LIGHT_HH_ +#define GZ_SIM_PYTHON__LIGHT_HH_ + +#include + +#include + +namespace gz +{ +namespace sim +{ +namespace python +{ +/// Define a pybind11 wrapper for a gz::sim::Light +/** + * \param[in] module a pybind11 module to add the definition to + */ +void +defineSimLight(pybind11::object module); +} // namespace python +} // namespace sim +} // namespace gz + +#endif // GZ_SIM_PYTHON__LIGHT_HH_ diff --git a/python/src/gz/sim/Link.cc b/python/src/gz/sim/Link.cc index 2031ba647c..51a6b15807 100644 --- a/python/src/gz/sim/Link.cc +++ b/python/src/gz/sim/Link.cc @@ -17,8 +17,6 @@ #include #include -#include - #include "Link.hh" namespace py = pybind11; diff --git a/python/src/gz/sim/_gz_sim_pybind11.cc b/python/src/gz/sim/_gz_sim_pybind11.cc index 9ac3ae9dfe..acf9373dc5 100644 --- a/python/src/gz/sim/_gz_sim_pybind11.cc +++ b/python/src/gz/sim/_gz_sim_pybind11.cc @@ -22,6 +22,7 @@ #include "EntityComponentManager.hh" #include "EventManager.hh" #include "Joint.hh" +#include "Light.hh" #include "Link.hh" #include "Model.hh" #include "Sensor.hh" @@ -39,6 +40,7 @@ PYBIND11_MODULE(BINDINGS_MODULE_NAME, m) { gz::sim::python::defineSimEntityComponentManager(m); gz::sim::python::defineSimEventManager(m); gz::sim::python::defineSimJoint(m); + gz::sim::python::defineSimLight(m); gz::sim::python::defineSimLink(m); gz::sim::python::defineSimModel(m); gz::sim::python::defineSimSensor(m); diff --git a/python/test/light_TEST.py b/python/test/light_TEST.py new file mode 100755 index 0000000000..ec1c4f6493 --- /dev/null +++ b/python/test/light_TEST.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# Copyright (C) 2023 Open Source Robotics Foundation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +from gz_test_deps.common import set_verbosity +from gz_test_deps.sim import (K_NULL_ENTITY, Light, + TestFixture, World, world_entity) +from gz_test_deps.math import Angle, Color, Pose3d, Vector3d + + +# This class test the Light class API created with pybind11. Since the setters +# methods require other systems to work (eg. Rendering sensors), those methods +# will not be fully tested, just verifying the method is being called. +class TestLight(unittest.TestCase): + post_iterations = 0 + iterations = 0 + pre_iterations = 0 + + def test_light(self): + set_verbosity(4) + + file_path = os.path.dirname(os.path.realpath(__file__)) + fixture = TestFixture(os.path.join(file_path, 'light_test.sdf')) + + def on_post_udpate_cb(_info, _ecm): + self.post_iterations += 1 + + def on_pre_udpate_cb(_info, _ecm): + self.pre_iterations += 1 + world_e = world_entity(_ecm) + self.assertNotEqual(K_NULL_ENTITY, world_e) + w = World(world_e) + light_point = Light(w.light_by_name(_ecm, 'light_point_test')) + light_dir = Light(w.light_by_name(_ecm, 'light_directional_test')) + light_spot = Light(w.light_by_name(_ecm, 'light_spot_test')) + # Entity Test + self.assertNotEqual(K_NULL_ENTITY, light_point.entity()) + self.assertNotEqual(K_NULL_ENTITY, light_dir.entity()) + self.assertNotEqual(K_NULL_ENTITY, light_spot.entity()) + # Valid Test + self.assertTrue(light_point.valid(_ecm)) + self.assertTrue(light_dir.valid(_ecm)) + self.assertTrue(light_spot.valid(_ecm)) + # Name Test + self.assertEqual('light_point_test', light_point.name(_ecm)) + self.assertEqual('light_directional_test', light_dir.name(_ecm)) + self.assertEqual('light_spot_test', light_spot.name(_ecm)) + # Pose Test + self.assertEqual(Pose3d(0, 2, 2, 0, 0, 0), light_point.pose(_ecm)) + light_point.set_pose(_ecm, Pose3d(4, 2, 2, 0, 0, 0)) + # Type Test + self.assertEqual('point', light_point.type(_ecm)) + self.assertEqual('directional', light_dir.type(_ecm)) + self.assertEqual('spot', light_spot.type(_ecm)) + # Diffuse Color Test + self.assertEqual(Color(1, 0, 0, 1), + light_point.diffuse_color(_ecm)) + light_point.set_diffuse_color(_ecm, Color(1, 1, 0, 1)) + # Specular Color Test + self.assertEqual(Color(0.2, 0, 0, 1), + light_point.specular_color(_ecm)) + light_point.set_specular_color(_ecm, Color(0.2, 0.2, 0, 1)) + # Cast Shadows Test + self.assertTrue(light_point.cast_shadows(_ecm)) + light_point.set_cast_shadows(_ecm, False) + # Intensity Test + self.assertEqual(2, light_point.intensity(_ecm)) + light_point.set_intensity(_ecm, 5) + # Direction Test + self.assertEqual(Vector3d(0.5, 0.5, -1), light_dir.direction(_ecm)) + light_dir.set_direction(_ecm, Vector3d(1, 0, -1)) + # Attenuation Range Test + self.assertEqual(20, light_point.attenuation_range(_ecm)) + light_point.set_attenuation_range(_ecm, 30) + # Attenuation Constant Test + self.assertEqual(0.8, light_point.attenuation_constant(_ecm)) + light_point.set_attenuation_constant(_ecm, 1.2) + # Attenuation Linear Test + self.assertEqual(0.2, light_point.attenuation_linear(_ecm)) + light_point.set_attenuation_linear(_ecm, 0.5) + # Attenuation Quadratric Test + self.assertEqual(0.01, light_point.attenuation_quadratic(_ecm)) + light_point.set_attenuation_quadratic(_ecm, 0.05) + # Spot Inner Angle Test + self.assertEqual(Angle(10), light_spot.spot_inner_angle(_ecm)) + light_spot.set_spot_inner_angle(_ecm, Angle(15)) + # Spot Outer Angle Test + self.assertEqual(Angle(5), light_spot.spot_outer_angle(_ecm)) + light_spot.set_spot_outer_angle(_ecm, Angle(10)) + # Spot Falloff Test + self.assertEqual(2, light_spot.spot_falloff(_ecm)) + light_spot.set_spot_falloff(_ecm, 4) + + def on_udpate_cb(_info, _ecm): + self.iterations += 1 + + fixture.on_post_update(on_post_udpate_cb) + fixture.on_update(on_udpate_cb) + fixture.on_pre_update(on_pre_udpate_cb) + fixture.finalize() + + server = fixture.server() + server.run(True, 2, False) + + self.assertEqual(2, self.pre_iterations) + self.assertEqual(2, self.iterations) + self.assertEqual(2, self.post_iterations) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/test/light_test.sdf b/python/test/light_test.sdf new file mode 100644 index 0000000000..bb53c012e9 --- /dev/null +++ b/python/test/light_test.sdf @@ -0,0 +1,32 @@ + + + + + + 0 2 2 0 0 0 + 1 0 0 1 + 0.2 0 0 1 + 2 + + 20 + 0.2 + 0.8 + 0.01 + + true + + + + 0.5 0.5 -1 + + + + + 10 + 5 + 2 + + + + +