diff --git a/AirLib/include/api/RpcLibClientBase.hpp b/AirLib/include/api/RpcLibClientBase.hpp index 9262120796..2a2fa5e3a4 100644 --- a/AirLib/include/api/RpcLibClientBase.hpp +++ b/AirLib/include/api/RpcLibClientBase.hpp @@ -130,6 +130,8 @@ namespace airlib msr::airlib::Kinematics::State simGetGroundTruthKinematics(const std::string& vehicle_name = "") const; msr::airlib::Environment::State simGetGroundTruthEnvironment(const std::string& vehicle_name = "") const; std::vector simSwapTextures(const std::string& tags, int tex_id = 0, int component_id = 0, int material_id = 0); + bool simSetObjectMaterial(const std::string& object_name, const std::string& material_name); + bool simSetObjectMaterialFromTexture(const std::string& object_name, const std::string& texture_path); // Recording APIs void startRecording(); diff --git a/AirLib/include/api/WorldSimApiBase.hpp b/AirLib/include/api/WorldSimApiBase.hpp index fdd1d184b7..291d8eb800 100644 --- a/AirLib/include/api/WorldSimApiBase.hpp +++ b/AirLib/include/api/WorldSimApiBase.hpp @@ -72,6 +72,8 @@ namespace airlib virtual bool runConsoleCommand(const std::string& command) = 0; virtual bool setObjectScale(const std::string& object_name, const Vector3r& scale) = 0; virtual std::unique_ptr> swapTextures(const std::string& tag, int tex_id = 0, int component_id = 0, int material_id = 0) = 0; + virtual bool setObjectMaterial(const std::string& object_name, const std::string& material_name) = 0; + virtual bool setObjectMaterialFromTexture(const std::string& object_name, const std::string& texture_path) = 0; virtual vector getMeshPositionVertexBuffers() const = 0; virtual bool createVoxelGrid(const Vector3r& position, const int& x_size, const int& y_size, const int& z_size, const float& res, const std::string& output_file) = 0; diff --git a/AirLib/src/api/RpcLibClientBase.cpp b/AirLib/src/api/RpcLibClientBase.cpp index 294ed98a6d..98e1f486ce 100644 --- a/AirLib/src/api/RpcLibClientBase.cpp +++ b/AirLib/src/api/RpcLibClientBase.cpp @@ -411,6 +411,16 @@ __pragma(warning(disable : 4239)) return pimpl_->client.call("simSwapTextures", tags, tex_id, component_id, material_id).as>(); } + bool RpcLibClientBase::simSetObjectMaterial(const std::string& object_name, const std::string& material_name) + { + return pimpl_->client.call("simSetObjectMaterial", object_name, material_name).as(); + } + + bool RpcLibClientBase::simSetObjectMaterialFromTexture(const std::string& object_name, const std::string& texture_path) + { + return pimpl_->client.call("simSetObjectMaterialFromTexture", object_name, texture_path).as(); + } + bool RpcLibClientBase::simLoadLevel(const string& level_name) { return pimpl_->client.call("simLoadLevel", level_name).as(); diff --git a/AirLib/src/api/RpcLibServerBase.cpp b/AirLib/src/api/RpcLibServerBase.cpp index b566001a8d..b8c120e814 100644 --- a/AirLib/src/api/RpcLibServerBase.cpp +++ b/AirLib/src/api/RpcLibServerBase.cpp @@ -397,6 +397,14 @@ namespace airlib return *getWorldSimApi()->swapTextures(tag, tex_id, component_id, material_id); }); + pimpl_->server.bind("simSetObjectMaterial", [&](const std::string& object_name, const std::string& material_name) -> bool { + return getWorldSimApi()->setObjectMaterial(object_name, material_name); + }); + + pimpl_->server.bind("simSetObjectMaterialFromTexture", [&](const std::string& object_name, const std::string& texture_path) -> bool { + return getWorldSimApi()->setObjectMaterialFromTexture(object_name, texture_path); + }); + pimpl_->server.bind("startRecording", [&]() -> void { getWorldSimApi()->startRecording(); }); diff --git a/PythonClient/airsim/client.py b/PythonClient/airsim/client.py index d62054add8..0fb13c2399 100644 --- a/PythonClient/airsim/client.py +++ b/PythonClient/airsim/client.py @@ -177,6 +177,33 @@ def simSwapTextures(self, tags, tex_id = 0, component_id = 0, material_id = 0): """ return self.client.call("simSwapTextures", tags, tex_id, component_id, material_id) + def simSetObjectMaterial(self, object_name, material_name): + """ + Runtime Swap Texture API + See https://microsoft.github.io/AirSim/retexturing/ for details + Args: + object_name (str): name of object to set material for + material_name (str): name of material to set for object + + Returns: + bool: True if material was set + """ + return self.client.call("simSetObjectMaterial", object_name, material_name) + + def simSetObjectMaterialFromTexture(self, object_name, texture_path): + """ + Runtime Swap Texture API + See https://microsoft.github.io/AirSim/retexturing/ for details + Args: + object_name (str): name of object to set material for + texture_path (str): path to texture to set for object + + Returns: + bool: True if material was set + """ + return self.client.call("simSetObjectMaterialFromTexture", object_name, texture_path) + + # time-of-day control def simSetTimeOfDay(self, is_enabled, start_datetime = "", is_start_datetime_dst = False, celestial_clock_speed = 1, update_interval_secs = 60, move_sun = True): """ diff --git a/PythonClient/environment/change_texture_example.py b/PythonClient/environment/change_texture_example.py new file mode 100644 index 0000000000..b10a8aff51 --- /dev/null +++ b/PythonClient/environment/change_texture_example.py @@ -0,0 +1,7 @@ +import airsim + +c = airsim.MultirotorClient() +c.confirmConnection() + +c.simSetObjectMaterialFromTexture("OrangeBall", "sample_texture.jpg") + diff --git a/PythonClient/environment/sample_texture.jpg b/PythonClient/environment/sample_texture.jpg new file mode 100644 index 0000000000..9081250511 Binary files /dev/null and b/PythonClient/environment/sample_texture.jpg differ diff --git a/Unity/AirLibWrapper/AirsimWrapper/Source/WorldSimApi.cpp b/Unity/AirLibWrapper/AirsimWrapper/Source/WorldSimApi.cpp index 66a7e021b3..efc56f51dc 100644 --- a/Unity/AirLibWrapper/AirsimWrapper/Source/WorldSimApi.cpp +++ b/Unity/AirLibWrapper/AirsimWrapper/Source/WorldSimApi.cpp @@ -67,6 +67,22 @@ std::unique_ptr> WorldSimApi::swapTextures(const std::s return result; } +bool WorldSimApi::setObjectMaterialFromTexture(const std::string& object_name, const std::string& texture_path) +{ + throw std::invalid_argument(common_utils::Utils::stringf( + "setObjectMaterialFromTexture is not supported on unity") + .c_str()); + return false; +} + +bool WorldSimApi::setObjectMaterial(const std::string& object_name, const std::string& material_name) +{ + throw std::invalid_argument(common_utils::Utils::stringf( + "setObjectMaterial is not supported on unity") + .c_str()); + return false; +} + std::vector WorldSimApi::listSceneObjects(const std::string& name_regex) const { std::vector result; diff --git a/Unity/AirLibWrapper/AirsimWrapper/Source/WorldSimApi.h b/Unity/AirLibWrapper/AirsimWrapper/Source/WorldSimApi.h index 02f7b64881..ca4302cd17 100644 --- a/Unity/AirLibWrapper/AirsimWrapper/Source/WorldSimApi.h +++ b/Unity/AirLibWrapper/AirsimWrapper/Source/WorldSimApi.h @@ -38,6 +38,8 @@ class WorldSimApi : public msr::airlib::WorldSimApiBase const std::string& message_param = "", unsigned char severity = 0) override; virtual std::unique_ptr> swapTextures(const std::string& tag, int tex_id = 0, int component_id = 0, int material_id = 0) override; + virtual bool setObjectMaterial(const std::string& object_name, const std::string& material_name) override; + virtual bool setObjectMaterialFromTexture(const std::string& object_name, const std::string& texture_path) override; virtual std::vector listSceneObjects(const std::string& name_regex) const override; virtual Pose getObjectPose(const std::string& object_name) const override; diff --git a/Unreal/Plugins/AirSim/Content/HUDAssets/DomainRandomizationMaterial.uasset b/Unreal/Plugins/AirSim/Content/HUDAssets/DomainRandomizationMaterial.uasset new file mode 100644 index 0000000000..a52dc9cf33 Binary files /dev/null and b/Unreal/Plugins/AirSim/Content/HUDAssets/DomainRandomizationMaterial.uasset differ diff --git a/Unreal/Plugins/AirSim/Source/SimMode/SimModeBase.cpp b/Unreal/Plugins/AirSim/Source/SimMode/SimModeBase.cpp index 1001c4c710..2ac7384bbe 100644 --- a/Unreal/Plugins/AirSim/Source/SimMode/SimModeBase.cpp +++ b/Unreal/Plugins/AirSim/Source/SimMode/SimModeBase.cpp @@ -64,6 +64,10 @@ ASimModeBase::ASimModeBase() } else loading_screen_widget_ = nullptr; + static ConstructorHelpers::FObjectFinder domain_rand_mat_finder(TEXT("Material'/AirSim/HUDAssets/DomainRandomizationMaterial.DomainRandomizationMaterial'")); + if (domain_rand_mat_finder.Succeeded()) { + domain_rand_material_ = domain_rand_mat_finder.Object; + } } void ASimModeBase::toggleLoadingScreen(bool is_visible) diff --git a/Unreal/Plugins/AirSim/Source/SimMode/SimModeBase.h b/Unreal/Plugins/AirSim/Source/SimMode/SimModeBase.h index 756f227d34..3b867d57a6 100644 --- a/Unreal/Plugins/AirSim/Source/SimMode/SimModeBase.h +++ b/Unreal/Plugins/AirSim/Source/SimMode/SimModeBase.h @@ -120,6 +120,7 @@ class AIRSIM_API ASimModeBase : public AActor TMap asset_map; TMap scene_object_map; + UMaterial* domain_rand_material_; protected: //must overrides typedef msr::airlib::AirSimSettings AirSimSettings; diff --git a/Unreal/Plugins/AirSim/Source/WorldSimApi.cpp b/Unreal/Plugins/AirSim/Source/WorldSimApi.cpp index d1f1b2f001..0edbd6f146 100644 --- a/Unreal/Plugins/AirSim/Source/WorldSimApi.cpp +++ b/Unreal/Plugins/AirSim/Source/WorldSimApi.cpp @@ -7,6 +7,7 @@ #include "DrawDebugHelpers.h" #include "Runtime/Engine/Classes/Components/LineBatchComponent.h" #include "Runtime/Engine/Classes/Engine/Engine.h" +#include "ImageUtils.h" #include #include @@ -431,6 +432,74 @@ std::unique_ptr> WorldSimApi::swapTextures(const std::s return swappedObjectNames; } +bool WorldSimApi::setObjectMaterialFromTexture(const std::string& object_name, const std::string& texture_path) +{ + bool success = false; + UAirBlueprintLib::RunCommandOnGameThread([this, &object_name, &texture_path, &success]() { + if (!IsValid(simmode_->domain_rand_material_)) { + UAirBlueprintLib::LogMessageString("Cannot find material for domain randomization", + "", + LogDebugLevel::Failure); + } + else { + UTexture2D* texture_desired = FImageUtils::ImportFileAsTexture2D(FString(texture_path.c_str())); + AActor* actor = UAirBlueprintLib::FindActor(simmode_, FString(object_name.c_str())); + + if (IsValid(actor)) { + TArray components; + actor->GetComponents(components); + for (UStaticMeshComponent* staticMeshComponent : components) { + UMaterialInstanceDynamic* dynamic_material = UMaterialInstanceDynamic::Create(simmode_->domain_rand_material_, staticMeshComponent); + dynamic_material->SetTextureParameterValue("TextureParameter", texture_desired); + staticMeshComponent->SetMaterial(0, dynamic_material); + } + success = true; + } + else { + UAirBlueprintLib::LogMessageString("Cannot find specified actor for domain randomization", + "", + LogDebugLevel::Failure); + } + } + }, + true); + + return success; +} + +bool WorldSimApi::setObjectMaterial(const std::string& object_name, const std::string& material_name) +{ + bool success = false; + UAirBlueprintLib::RunCommandOnGameThread([this, &object_name, &material_name, &success]() { + AActor* actor = UAirBlueprintLib::FindActor(simmode_, FString(object_name.c_str())); + UMaterial* material = static_cast(StaticLoadObject(UMaterial::StaticClass(), nullptr, *FString(material_name.c_str()))); + + if (!IsValid(material)) { + UAirBlueprintLib::LogMessageString("Cannot find specified material for domain randomization", + "", + LogDebugLevel::Failure); + } + else { + if (IsValid(actor)) { + TArray components; + actor->GetComponents(components); + for (UStaticMeshComponent* staticMeshComponent : components) { + staticMeshComponent->SetMaterial(0, material); + } + success = true; + } + else { + UAirBlueprintLib::LogMessageString("Cannot find specified actor for domain randomization", + "", + LogDebugLevel::Failure); + } + } + }, + true); + + return success; +} + //----------- Plotting APIs ----------/ void WorldSimApi::simFlushPersistentMarkers() { diff --git a/Unreal/Plugins/AirSim/Source/WorldSimApi.h b/Unreal/Plugins/AirSim/Source/WorldSimApi.h index 5e38548787..dde025b701 100644 --- a/Unreal/Plugins/AirSim/Source/WorldSimApi.h +++ b/Unreal/Plugins/AirSim/Source/WorldSimApi.h @@ -48,6 +48,8 @@ class WorldSimApi : public msr::airlib::WorldSimApiBase const std::string& message_param = "", unsigned char severity = 0) override; virtual std::unique_ptr> swapTextures(const std::string& tag, int tex_id = 0, int component_id = 0, int material_id = 0) override; + virtual bool setObjectMaterial(const std::string& object_name, const std::string& material_name) override; + virtual bool setObjectMaterialFromTexture(const std::string& object_name, const std::string& texture_path) override; virtual std::vector listSceneObjects(const std::string& name_regex) const override; virtual Pose getObjectPose(const std::string& object_name) const override; virtual bool setObjectPose(const std::string& object_name, const Pose& pose, bool teleport) override;