diff --git a/tests/conftest.py b/tests/conftest.py index e5cfadcde..911b27b0f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,6 @@ import os.path from pathlib import Path import re -import subprocess import sys import pytest @@ -52,6 +51,17 @@ def loader(relpath, **kwargs): return loader +@pytest.fixture +def launchWebots(): + DISPLAY = os.environ.get("DISPLAY") + if not DISPLAY: + pytest.skip("DISPLAY env variable not set.") + WEBOTS_ROOT = os.environ.get("WEBOTS_ROOT") + if not WEBOTS_ROOT: + pytest.skip("WEBOTS_ROOT env variable not set.") + return WEBOTS_ROOT + + ## Command-line options diff --git a/tests/simulators/webots/dynamic/dynamic.scenic b/tests/simulators/webots/dynamic/dynamic.scenic new file mode 100644 index 000000000..96c897102 --- /dev/null +++ b/tests/simulators/webots/dynamic/dynamic.scenic @@ -0,0 +1,27 @@ +""" +Create a Webots cube in air and have it drop +""" + +model scenic.simulators.webots.model + +class Floor(Object): + width: 5 + length: 5 + height: 0.01 + position: (0,0,0) + color: [0.785, 0.785, 0.785] + +class Block(WebotsObject): + webotsAdhoc: {'physics': True} + shape: BoxShape() + width: 0.2 + length: 0.2 + height: 0.2 + density: 100 + color: [1, 0.502, 0] + +floor = new Floor +ego = new Block at (0, 0, 0.5) #above floor by 0.5 + +terminate when ego.z < 0.1 +record (ego.z) as BlockPosition \ No newline at end of file diff --git a/tests/simulators/webots/dynamic/webots_data/controllers/scenic_supervisor/scenic_supervisor.py b/tests/simulators/webots/dynamic/webots_data/controllers/scenic_supervisor/scenic_supervisor.py new file mode 100644 index 000000000..8e875c6a8 --- /dev/null +++ b/tests/simulators/webots/dynamic/webots_data/controllers/scenic_supervisor/scenic_supervisor.py @@ -0,0 +1,30 @@ +import os + +from controller import Supervisor + +import scenic +from scenic.simulators.webots import WebotsSimulator + +WEBOTS_RESULT_FILE_PATH = f"{os.path.dirname(__file__)}/../../../results.txt" + + +def send_results(data): + with open(WEBOTS_RESULT_FILE_PATH, "w") as file: + file.write(data) + + +supervisor = Supervisor() +simulator = WebotsSimulator(supervisor) + +path = supervisor.getCustomData() +print(f"Loading Scenic scenario {path}") +scenario = scenic.scenarioFromFile(path) + +scene, _ = scenario.generate() +simulation = simulator.simulate(scene, verbosity=2) +block_movements = simulation.result.records["BlockPosition"] +first_block_movement = block_movements[0] +last_block_movement = block_movements[-1] +blocks = [first_block_movement, last_block_movement] +supervisor.simulationQuit(status="finished") +send_results(str(blocks)) diff --git a/tests/simulators/webots/dynamic/webots_data/protos/ScenicObject.proto b/tests/simulators/webots/dynamic/webots_data/protos/ScenicObject.proto new file mode 100644 index 000000000..605321782 --- /dev/null +++ b/tests/simulators/webots/dynamic/webots_data/protos/ScenicObject.proto @@ -0,0 +1,28 @@ +#VRML_SIM R2023a utf8 + +PROTO ScenicObject [ + field SFVec3f translation 0 0 0 + field SFRotation rotation 0 0 1 0 + field SFString name "solid" + field SFVec3f angularVelocity 0 0 0 + field MFString url "" +] +{ + Solid { + translation IS translation + rotation IS rotation + name IS name + angularVelocity IS angularVelocity + children [ + CadShape { + url IS url + castShadows FALSE + } + ] + boundingObject Shape { + geometry Mesh { + url IS url + } + } + } +} diff --git a/tests/simulators/webots/dynamic/webots_data/protos/ScenicObjectWithPhysics.proto b/tests/simulators/webots/dynamic/webots_data/protos/ScenicObjectWithPhysics.proto new file mode 100644 index 000000000..cf1d42934 --- /dev/null +++ b/tests/simulators/webots/dynamic/webots_data/protos/ScenicObjectWithPhysics.proto @@ -0,0 +1,34 @@ +#VRML_SIM R2023a utf8 + +PROTO ScenicObjectWithPhysics [ + field SFVec3f translation 0 0 0 + field SFRotation rotation 0 0 1 0 + field SFString name "solid" + field SFVec3f angularVelocity 0 0 0 + field MFString url "" + field SFFloat density 1000 # kg / m^3 +] +{ + Solid { + translation IS translation + rotation IS rotation + name IS name + angularVelocity IS angularVelocity + children [ + CadShape { + url IS url + castShadows FALSE + } + ] + boundingObject Shape { + geometry Mesh { + url IS url + } + } + physics Physics { + # density will be set by the simulator + density IS density + mass -1 + } + } +} diff --git a/tests/simulators/webots/dynamic/webots_data/worlds/world.wbt b/tests/simulators/webots/dynamic/webots_data/worlds/world.wbt new file mode 100644 index 000000000..0ab0be278 --- /dev/null +++ b/tests/simulators/webots/dynamic/webots_data/worlds/world.wbt @@ -0,0 +1,34 @@ +#VRML_SIM R2023a utf8 + +EXTERNPROTO "https://raw.githubusercontent.com/cyberbotics/webots/R2023a/projects/objects/backgrounds/protos/TexturedBackground.proto" +EXTERNPROTO "https://raw.githubusercontent.com/cyberbotics/webots/R2023a/projects/objects/floors/protos/Floor.proto" +IMPORTABLE EXTERNPROTO "../protos/ScenicObject.proto" +IMPORTABLE EXTERNPROTO "../protos/ScenicObjectWithPhysics.proto" + +WorldInfo { + gravity 3 + basicTimeStep 16 + contactProperties [ + ContactProperties { + coulombFriction [ + 0.8 + ] + } + ] +} +Viewpoint { + orientation -0.19729161510865992 0.07408415124219735 0.977541588446517 2.4380019050245862 + position 6.683615307234937 -5.5006366813466805 4.16419445995153 +} +TexturedBackground { +} +Floor { + name "FLOOR" + size 5 5 +} +Robot { + name "Supervisor" + controller "scenic_supervisor" + customData "../../../dynamic.scenic" + supervisor TRUE +} diff --git a/tests/simulators/webots/test_webots.py b/tests/simulators/webots/test_webots.py index e7904532a..394c6aeb7 100644 --- a/tests/simulators/webots/test_webots.py +++ b/tests/simulators/webots/test_webots.py @@ -1,7 +1,42 @@ +import os +import subprocess + import pytest from tests.utils import pickle_test, sampleScene, tryPickling +WEBOTS_RESULTS_FILE_PATH = f"{os.path.dirname(__file__)}/dynamic/results.txt" +WEBOTS_WORLD_FILE_PATH = ( + f"{os.path.dirname(__file__)}/dynamic/webots_data/worlds/world.wbt" +) + + +def receive_results(): + with open(WEBOTS_RESULTS_FILE_PATH, "r") as file: + content = file.read() + return content + + +def cleanup_results(): + command = f"rm -f {WEBOTS_RESULTS_FILE_PATH}" + subprocess.run(command, shell=True) + + +def test_dynamics_scenarios(launchWebots): + WEBOTS_ROOT = launchWebots + cleanup_results() + command = f"bash {WEBOTS_ROOT}/webots --no-rendering --minimize --batch {WEBOTS_WORLD_FILE_PATH}" + subprocess.run(command, shell=True) + data = receive_results() + assert data != None + start_z = float(data.split(",")[1].strip(" )]")) + end_z = float(data.split(",")[3].strip(" )]")) + assert start_z == 0.5 + assert start_z > end_z + expected_value = 0.09 + tolerance = 0.01 + assert end_z == pytest.approx(expected_value, abs=tolerance) + def test_basic(loadLocalScenario): scenario = loadLocalScenario("basic.scenic")