Moved code from Sensor to ActorBlueprintFunctionLibrary
Added documentation Minor code improvements
This commit is contained in:
parent
1e5195dd9a
commit
79b7e39196
|
@ -311,14 +311,19 @@ sensor.other.obstacle
|
||||||
|
|
||||||
This sensor, when attached to an actor, reports if there is obstacles ahead.
|
This sensor, when attached to an actor, reports if there is obstacles ahead.
|
||||||
|
|
||||||
|
| Blueprint attribute | Type | Default | Description |
|
||||||
|
| -------------------- | ---- | ------- | ----------- |
|
||||||
|
| `distance` | float | 5 | Distance to throw the trace to |
|
||||||
|
| `hit_radius` | float | 0.5 | Radius of the trace |
|
||||||
|
| `only_dynamics` | bool | false | If true, the trace will only look for dynamic objects |
|
||||||
|
| `debug_linetrace` | bool | false | If true, the trace will be visible |
|
||||||
|
|
||||||
This sensor produces
|
This sensor produces
|
||||||
[`carla.ObstacleDetectionSensorEvent`](python_api.md#carlaobstacledetectionsensoreventdata)
|
[`carla.ObstacleDetectionSensorEvent`](python_api.md#carlaobstacledetectionsensoreventdata)
|
||||||
objects.
|
objects.
|
||||||
|
|
||||||
| Sensor data attribute | Type | Description |
|
| Sensor data attribute | Type | Description |
|
||||||
| ---------------------- | ----------- | ----------- |
|
| ---------------------- | ----------- | ----------- |
|
||||||
| `distance` | float | Distance to throw the trace to |
|
| `actor` | carla.Actor | Actor that detected the obstacle ("self" actor) |
|
||||||
| `hitradius` | float | Radius of the trace |
|
| `other_actor` | carla.Actor | Actor detected as obstacle |
|
||||||
| `heightvar` | float | Modified height from which the trace will start and end |
|
| `distance ` | float | Distance from actor to other_actor |
|
||||||
| `onlydynamics` | bool | Flag to indicate that only dynamics will be traced |
|
|
||||||
| `debuglinetrace` | bool | Flag to indicate whether the linetrace will be shown or not |
|
|
|
@ -166,6 +166,12 @@
|
||||||
- `longitude`
|
- `longitude`
|
||||||
- `altitude`
|
- `altitude`
|
||||||
|
|
||||||
|
## `carla.ObstacleDetectionSensorEvent(carla.SensorData)`
|
||||||
|
|
||||||
|
- `actor`
|
||||||
|
- `other_actor`
|
||||||
|
- `distance`
|
||||||
|
|
||||||
## `carla.VehicleControl`
|
## `carla.VehicleControl`
|
||||||
|
|
||||||
- `throttle`
|
- `throttle`
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
|
|
||||||
#include "carla/Debug.h"
|
#include "carla/Debug.h"
|
||||||
#include "carla/client/detail/ActorVariant.h"
|
#include "carla/client/detail/ActorVariant.h"
|
||||||
#include "carla/geom/Vector3D.h"
|
|
||||||
#include "carla/sensor/SensorData.h"
|
#include "carla/sensor/SensorData.h"
|
||||||
#include "carla/sensor/s11n/ObstacleDetectionEventSerializer.h"
|
#include "carla/sensor/s11n/ObstacleDetectionEventSerializer.h"
|
||||||
|
|
||||||
|
|
||||||
namespace carla {
|
namespace carla {
|
||||||
namespace sensor {
|
namespace sensor {
|
||||||
namespace data {
|
namespace data {
|
||||||
|
@ -27,9 +27,10 @@ namespace data {
|
||||||
|
|
||||||
explicit ObstacleDetectionEvent(const RawData &data)
|
explicit ObstacleDetectionEvent(const RawData &data)
|
||||||
: Super(data),
|
: Super(data),
|
||||||
_self_actor(Serializer::DeserializeRawData(data).self_actor),
|
_deserialized_data(Serializer::DeserializeRawData(data)),
|
||||||
_other_actor(Serializer::DeserializeRawData(data).other_actor),
|
_self_actor(_deserialized_data.self_actor),
|
||||||
_distance(Serializer::DeserializeRawData(data).distance) {}
|
_other_actor(_deserialized_data.other_actor),
|
||||||
|
_distance(_deserialized_data.distance) {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -50,11 +51,14 @@ namespace data {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
Serializer::Data _deserialized_data;
|
||||||
|
|
||||||
client::detail::ActorVariant _self_actor;
|
client::detail::ActorVariant _self_actor;
|
||||||
|
|
||||||
client::detail::ActorVariant _other_actor;
|
client::detail::ActorVariant _other_actor;
|
||||||
|
|
||||||
float _distance;
|
float _distance;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace data
|
} // namespace data
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
#include "carla/Debug.h"
|
#include "carla/Debug.h"
|
||||||
#include "carla/Memory.h"
|
#include "carla/Memory.h"
|
||||||
#include "carla/rpc/Actor.h"
|
#include "carla/rpc/Actor.h"
|
||||||
#include "carla/geom/Vector3D.h"
|
|
||||||
#include "carla/sensor/RawData.h"
|
#include "carla/sensor/RawData.h"
|
||||||
|
|
||||||
namespace carla {
|
namespace carla {
|
||||||
|
|
|
@ -441,6 +441,46 @@ void UActorBlueprintFunctionLibrary::MakePedestrianDefinitions(
|
||||||
FillActorDefinitionArray(ParameterArray, Definitions, &MakePedestrianDefinition);
|
FillActorDefinitionArray(ParameterArray, Definitions, &MakePedestrianDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UActorBlueprintFunctionLibrary::MakeObstacleDetectorDefinitions(
|
||||||
|
const FString &Type,
|
||||||
|
const FString &Id,
|
||||||
|
FActorDefinition &Definition)
|
||||||
|
{
|
||||||
|
Definition = MakeGenericSensorDefinition(TEXT("other"), TEXT("obstacle"));
|
||||||
|
|
||||||
|
// Distance.
|
||||||
|
FActorVariation distance;
|
||||||
|
distance.Id = TEXT("distance");
|
||||||
|
distance.Type = EActorAttributeType::Float;
|
||||||
|
distance.RecommendedValues = { TEXT("5.0") };
|
||||||
|
distance.bRestrictToRecommended = false;
|
||||||
|
// HitRadius.
|
||||||
|
FActorVariation hitradius;
|
||||||
|
hitradius.Id = TEXT("hit_radius");
|
||||||
|
hitradius.Type = EActorAttributeType::Float;
|
||||||
|
hitradius.RecommendedValues = { TEXT("0.5") };
|
||||||
|
hitradius.bRestrictToRecommended = false;
|
||||||
|
// Only Dynamics
|
||||||
|
FActorVariation onlydynamics;
|
||||||
|
onlydynamics.Id = TEXT("only_dynamics");
|
||||||
|
onlydynamics.Type = EActorAttributeType::Bool;
|
||||||
|
onlydynamics.RecommendedValues = { TEXT("false") };
|
||||||
|
onlydynamics.bRestrictToRecommended = false;
|
||||||
|
// Debug Line Trace
|
||||||
|
FActorVariation debuglinetrace;
|
||||||
|
debuglinetrace.Id = TEXT("debug_linetrace");
|
||||||
|
debuglinetrace.Type = EActorAttributeType::Bool;
|
||||||
|
debuglinetrace.RecommendedValues = { TEXT("false") };
|
||||||
|
debuglinetrace.bRestrictToRecommended = false;
|
||||||
|
|
||||||
|
Definition.Variations.Append({
|
||||||
|
distance,
|
||||||
|
hitradius,
|
||||||
|
onlydynamics,
|
||||||
|
debuglinetrace
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
/// ============================================================================
|
/// ============================================================================
|
||||||
/// -- Helpers to retrieve attribute values ------------------------------------
|
/// -- Helpers to retrieve attribute values ------------------------------------
|
||||||
/// ============================================================================
|
/// ============================================================================
|
||||||
|
|
|
@ -91,6 +91,12 @@ public:
|
||||||
const TArray<FPedestrianParameters> &ParameterArray,
|
const TArray<FPedestrianParameters> &ParameterArray,
|
||||||
TArray<FActorDefinition> &Definitions);
|
TArray<FActorDefinition> &Definitions);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
static void MakeObstacleDetectorDefinitions(
|
||||||
|
const FString &Type,
|
||||||
|
const FString &Id,
|
||||||
|
FActorDefinition &Definition);
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
/// ==========================================================================
|
/// ==========================================================================
|
||||||
/// @name Helpers to retrieve attribute values
|
/// @name Helpers to retrieve attribute values
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#include "Carla/Actor/ActorBlueprintFunctionLibrary.h"
|
#include "Carla/Actor/ActorBlueprintFunctionLibrary.h"
|
||||||
#include "Carla/Actor/ActorRegistry.h"
|
#include "Carla/Actor/ActorRegistry.h"
|
||||||
#include "Carla/Game/CarlaEpisode.h"
|
#include "Carla/Game/CarlaEpisode.h"
|
||||||
#include "Carla/Game/CarlaStatics.h"
|
#include "Carla/Game/CarlaGameInstance.h"
|
||||||
#include "Carla/Game/TheNewCarlaGameModeBase.h"
|
#include "Carla/Game/TheNewCarlaGameModeBase.h"
|
||||||
|
|
||||||
AObstacleDetectionSensor::AObstacleDetectionSensor(const FObjectInitializer &ObjectInitializer)
|
AObstacleDetectionSensor::AObstacleDetectionSensor(const FObjectInitializer &ObjectInitializer)
|
||||||
|
@ -21,78 +21,29 @@ AObstacleDetectionSensor::AObstacleDetectionSensor(const FObjectInitializer &Obj
|
||||||
|
|
||||||
FActorDefinition AObstacleDetectionSensor::GetSensorDefinition()
|
FActorDefinition AObstacleDetectionSensor::GetSensorDefinition()
|
||||||
{
|
{
|
||||||
auto SensorDefinition = FActorDefinition();
|
FActorDefinition SensorDefinition = FActorDefinition();
|
||||||
FillIdAndTags(SensorDefinition, TEXT("sensor"), TEXT("other"), TEXT("obstacle"));
|
UActorBlueprintFunctionLibrary::MakeObstacleDetectorDefinitions(TEXT("other"), TEXT("obstacle"), SensorDefinition);
|
||||||
AddRecommendedValuesForSensorRoleNames(SensorDefinition);
|
|
||||||
AddVariationsForSensor(SensorDefinition);
|
|
||||||
|
|
||||||
// Distance.
|
|
||||||
FActorVariation distance;
|
|
||||||
distance.Id = TEXT("distance");
|
|
||||||
distance.Type = EActorAttributeType::Float;
|
|
||||||
distance.RecommendedValues = { TEXT("500.0") };
|
|
||||||
distance.bRestrictToRecommended = false;
|
|
||||||
// HitRadius.
|
|
||||||
FActorVariation hitradius;
|
|
||||||
hitradius.Id = TEXT("hitradius");
|
|
||||||
hitradius.Type = EActorAttributeType::Float;
|
|
||||||
hitradius.RecommendedValues = { TEXT("50.0") };
|
|
||||||
hitradius.bRestrictToRecommended = false;
|
|
||||||
// Height Variation
|
|
||||||
FActorVariation heightvar;
|
|
||||||
heightvar.Id = TEXT("heightvar");
|
|
||||||
heightvar.Type = EActorAttributeType::Float;
|
|
||||||
heightvar.RecommendedValues = { TEXT("100.0") };
|
|
||||||
heightvar.bRestrictToRecommended = false;
|
|
||||||
// Only Dynamics
|
|
||||||
FActorVariation onlydynamics;
|
|
||||||
onlydynamics.Id = TEXT("onlydynamics");
|
|
||||||
onlydynamics.Type = EActorAttributeType::Bool;
|
|
||||||
onlydynamics.RecommendedValues = { TEXT("false") };
|
|
||||||
onlydynamics.bRestrictToRecommended = false;
|
|
||||||
// Debug Line Trace
|
|
||||||
FActorVariation debuglinetrace;
|
|
||||||
debuglinetrace.Id = TEXT("debuglinetrace");
|
|
||||||
debuglinetrace.Type = EActorAttributeType::Bool;
|
|
||||||
debuglinetrace.RecommendedValues = { TEXT("false") };
|
|
||||||
debuglinetrace.bRestrictToRecommended = false;
|
|
||||||
|
|
||||||
SensorDefinition.Variations.Append({
|
|
||||||
distance,
|
|
||||||
hitradius,
|
|
||||||
heightvar,
|
|
||||||
onlydynamics,
|
|
||||||
debuglinetrace
|
|
||||||
});
|
|
||||||
return SensorDefinition;
|
return SensorDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AObstacleDetectionSensor::SetOwner(AActor *NewOwner)
|
|
||||||
{
|
|
||||||
Super::SetOwner(NewOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AObstacleDetectionSensor::Set(const FActorDescription &Description)
|
void AObstacleDetectionSensor::Set(const FActorDescription &Description)
|
||||||
{
|
{
|
||||||
|
//Multiplying numbers for 100 in order to convert from meters to centimeters
|
||||||
Super::Set(Description);
|
Super::Set(Description);
|
||||||
Distance = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToFloat(
|
Distance = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToFloat(
|
||||||
"distance",
|
"distance",
|
||||||
Description.Variations,
|
Description.Variations,
|
||||||
Distance);
|
Distance) * 100;
|
||||||
HitRadius = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToFloat(
|
HitRadius = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToFloat(
|
||||||
"hitradius",
|
"hit_radius",
|
||||||
Description.Variations,
|
Description.Variations,
|
||||||
HitRadius);
|
HitRadius) * 100;
|
||||||
HeightVar = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToFloat(
|
|
||||||
"heightvar",
|
|
||||||
Description.Variations,
|
|
||||||
HeightVar);
|
|
||||||
bOnlyDynamics = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToBool(
|
bOnlyDynamics = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToBool(
|
||||||
"onlydynamics",
|
"only_dynamics",
|
||||||
Description.Variations,
|
Description.Variations,
|
||||||
bOnlyDynamics);
|
bOnlyDynamics);
|
||||||
bDebugLineTrace = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToBool(
|
bDebugLineTrace = UActorBlueprintFunctionLibrary::RetrieveActorAttributeToBool(
|
||||||
"debuglinetrace",
|
"debug_linetrace",
|
||||||
Description.Variations,
|
Description.Variations,
|
||||||
bDebugLineTrace);
|
bDebugLineTrace);
|
||||||
|
|
||||||
|
@ -102,38 +53,36 @@ void AObstacleDetectionSensor::BeginPlay()
|
||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
Episode = UCarlaStatics::GetCurrentEpisode(GetWorld());
|
auto *GameMode = Cast<ATheNewCarlaGameModeBase>(GetWorld()->GetAuthGameMode());
|
||||||
if (Episode == nullptr)
|
|
||||||
|
if (GameMode == nullptr)
|
||||||
{
|
{
|
||||||
UE_LOG(LogCarla, Error, TEXT("AObstacleDetectionSensor: cannot find a valid CarlaEpisode"));
|
UE_LOG(LogCarla, Error, TEXT("AObstacleDetectionSensor: Game mode not compatible with this sensor"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Episode = &GameMode->GetCarlaEpisode();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AObstacleDetectionSensor::Tick(float DeltaSeconds)
|
void AObstacleDetectionSensor::Tick(float DeltaSeconds)
|
||||||
{
|
{
|
||||||
Super::Tick(DeltaSeconds);
|
Super::Tick(DeltaSeconds);
|
||||||
|
|
||||||
// The vectors should get raised a little bit to avoid hitting the ground.
|
const FVector &Start = GetActorLocation();
|
||||||
// Temp fix until the collision channels get fixed
|
const FVector &End = Start + (GetActorForwardVector() * Distance);
|
||||||
const FVector &Start = Super::GetOwner()->GetActorLocation() + FVector(0, 0, HeightVar);
|
UWorld* currentWorld = GetWorld();
|
||||||
const FVector &End = Start + (Super::GetOwner()->GetActorForwardVector() * Distance);
|
|
||||||
|
|
||||||
// Struct in which the result of the scan will be saved
|
// Struct in which the result of the scan will be saved
|
||||||
FHitResult HitOut = FHitResult();
|
FHitResult HitOut = FHitResult();
|
||||||
|
|
||||||
// Get World Source
|
|
||||||
TObjectIterator<APlayerController> PlayerController;
|
|
||||||
|
|
||||||
// Initialization of Query Parameters
|
// Initialization of Query Parameters
|
||||||
FCollisionQueryParams TraceParams(FName(TEXT("ObstacleDetection Trace")), true, Super::GetOwner());
|
FCollisionQueryParams TraceParams(FName(TEXT("ObstacleDetection Trace")), true, this);
|
||||||
|
|
||||||
// If debug mode enabled, we create a tag that will make the sweep be
|
// If debug mode enabled, we create a tag that will make the sweep be
|
||||||
// displayed.
|
// displayed.
|
||||||
if (bDebugLineTrace)
|
if (bDebugLineTrace)
|
||||||
{
|
{
|
||||||
const FName TraceTag("ObstacleDebugTrace");
|
const FName TraceTag("ObstacleDebugTrace");
|
||||||
PlayerController->GetWorld()->DebugDrawTraceTag = TraceTag;
|
currentWorld->DebugDrawTraceTag = TraceTag;
|
||||||
TraceParams.TraceTag = TraceTag;
|
TraceParams.TraceTag = TraceTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +96,9 @@ void AObstacleDetectionSensor::Tick(float DeltaSeconds)
|
||||||
TraceParams.bReturnPhysicalMaterial = false;
|
TraceParams.bReturnPhysicalMaterial = false;
|
||||||
|
|
||||||
// Ignore ourselves
|
// Ignore ourselves
|
||||||
TraceParams.AddIgnoredActor(Super::GetOwner());
|
TraceParams.AddIgnoredActor(this);
|
||||||
|
if(Super::GetOwner()!=nullptr)
|
||||||
|
TraceParams.AddIgnoredActor(Super::GetOwner());
|
||||||
|
|
||||||
bool isHitReturned;
|
bool isHitReturned;
|
||||||
// Choosing a type of sweep is a workaround until everything get properly
|
// Choosing a type of sweep is a workaround until everything get properly
|
||||||
|
@ -157,7 +108,7 @@ void AObstacleDetectionSensor::Tick(float DeltaSeconds)
|
||||||
// If we go only for dynamics, we check the object type AllDynamicObjects
|
// If we go only for dynamics, we check the object type AllDynamicObjects
|
||||||
FCollisionObjectQueryParams TraceChannel = FCollisionObjectQueryParams(
|
FCollisionObjectQueryParams TraceChannel = FCollisionObjectQueryParams(
|
||||||
FCollisionObjectQueryParams::AllDynamicObjects);
|
FCollisionObjectQueryParams::AllDynamicObjects);
|
||||||
isHitReturned = PlayerController->GetWorld()->SweepSingleByObjectType(
|
isHitReturned = currentWorld->SweepSingleByObjectType(
|
||||||
HitOut,
|
HitOut,
|
||||||
Start,
|
Start,
|
||||||
End,
|
End,
|
||||||
|
@ -171,7 +122,7 @@ void AObstacleDetectionSensor::Tick(float DeltaSeconds)
|
||||||
// Else, if we go for everything, we get everything that interacts with a
|
// Else, if we go for everything, we get everything that interacts with a
|
||||||
// Pawn
|
// Pawn
|
||||||
ECollisionChannel TraceChannel = ECC_WorldStatic;
|
ECollisionChannel TraceChannel = ECC_WorldStatic;
|
||||||
isHitReturned = PlayerController->GetWorld()->SweepSingleByChannel(
|
isHitReturned = currentWorld->SweepSingleByChannel(
|
||||||
HitOut,
|
HitOut,
|
||||||
Start,
|
Start,
|
||||||
End,
|
End,
|
||||||
|
@ -183,9 +134,8 @@ void AObstacleDetectionSensor::Tick(float DeltaSeconds)
|
||||||
|
|
||||||
if (isHitReturned)
|
if (isHitReturned)
|
||||||
{
|
{
|
||||||
OnObstacleDetectionEvent(Super::GetOwner(), HitOut.Actor.Get(), HitOut.Distance, HitOut);
|
OnObstacleDetectionEvent(this, HitOut.Actor.Get(), HitOut.Distance, HitOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AObstacleDetectionSensor::OnObstacleDetectionEvent(
|
void AObstacleDetectionSensor::OnObstacleDetectionEvent(
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Carla/Sensor/Sensor.h"
|
#include "Carla/Sensor/Sensor.h"
|
||||||
|
#include "Carla/Actor/ActorDefinition.h"
|
||||||
|
#include "Carla/Actor/ActorDescription.h"
|
||||||
#include "ObstacleDetectionSensor.generated.h"
|
#include "ObstacleDetectionSensor.generated.h"
|
||||||
|
|
||||||
class UCarlaEpisode;
|
class UCarlaEpisode;
|
||||||
|
@ -18,24 +19,12 @@ class CARLA_API AObstacleDetectionSensor : public ASensor
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
float Distance;
|
|
||||||
|
|
||||||
float HitRadius;
|
|
||||||
|
|
||||||
float HeightVar;
|
|
||||||
|
|
||||||
bool bOnlyDynamics = false;
|
|
||||||
|
|
||||||
bool bDebugLineTrace = false;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static FActorDefinition GetSensorDefinition();
|
static FActorDefinition GetSensorDefinition();
|
||||||
|
|
||||||
AObstacleDetectionSensor(const FObjectInitializer &ObjectInitializer);
|
AObstacleDetectionSensor(const FObjectInitializer &ObjectInitializer);
|
||||||
|
|
||||||
void SetOwner(AActor *NewOwner) override;
|
|
||||||
|
|
||||||
void Set(const FActorDescription &Description) override;
|
void Set(const FActorDescription &Description) override;
|
||||||
|
|
||||||
void BeginPlay() override;
|
void BeginPlay() override;
|
||||||
|
@ -53,4 +42,15 @@ private:
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
const UCarlaEpisode *Episode = nullptr;
|
const UCarlaEpisode *Episode = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
float Distance;
|
||||||
|
|
||||||
|
float HitRadius;
|
||||||
|
|
||||||
|
bool bOnlyDynamics = false;
|
||||||
|
|
||||||
|
bool bDebugLineTrace = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue