mhefny
5 years ago
committed by
Francisco Ferreira
13 changed files with 1354 additions and 1 deletions
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
# Copyright 1996-2019 Cyberbotics Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
### Generic Makefile.include for Webots controllers, physics plugins, robot
|
||||
### window libraries, remote control libraries and other libraries
|
||||
### to be used with GNU make
|
||||
###
|
||||
### Platforms: Windows, macOS, Linux
|
||||
### Languages: C, C++
|
||||
###
|
||||
### Authors: Olivier Michel, Yvan Bourquin, Fabien Rohrer
|
||||
### Edmund Ronald, Sergei Poskriakov
|
||||
###
|
||||
###-----------------------------------------------------------------------------
|
||||
###
|
||||
### This file is meant to be included from the Makefile files located in the
|
||||
### Webots projects subdirectories. It is possible to set a number of variables
|
||||
### to customize the build process, i.e., add source files, compilation flags,
|
||||
### include paths, libraries, etc. These variables should be set in your local
|
||||
### Makefile just before including this Makefile.include. This Makefile.include
|
||||
### should never be modified.
|
||||
###
|
||||
### Here is a description of the variables you may set in your local Makefile:
|
||||
###
|
||||
### ---- C Sources ----
|
||||
### if your program uses several C source files:
|
||||
### C_SOURCES = my_plugin.c my_clever_algo.c my_graphics.c
|
||||
###
|
||||
### ---- C++ Sources ----
|
||||
### if your program uses several C++ source files:
|
||||
### CXX_SOURCES = my_plugin.cc my_clever_algo.cpp my_graphics.c++
|
||||
###
|
||||
### ---- Compilation options ----
|
||||
### if special compilation flags are necessary:
|
||||
### CFLAGS = -Wno-unused-result
|
||||
###
|
||||
### ---- Linked libraries ----
|
||||
### if your program needs additional libraries:
|
||||
### INCLUDE = -I"/my_library_path/include"
|
||||
### LIBRARIES = -L"/path/to/my/library" -lmy_library -lmy_other_library
|
||||
###
|
||||
### ---- Linking options ----
|
||||
### if special linking flags are needed:
|
||||
### LFLAGS = -s
|
||||
###
|
||||
### ---- Webots included libraries ----
|
||||
### if you want to use the Webots C API in your C++ controller program:
|
||||
### USE_C_API = true
|
||||
### if you want to link with the Qt framework embedded in Webots:
|
||||
### QT = core gui widgets network
|
||||
###
|
||||
### ---- Debug mode ----
|
||||
### if you want to display the gcc command line for compilation and link, as
|
||||
### well as the rm command details used for cleaning:
|
||||
### VERBOSE = 1
|
||||
###
|
||||
###-----------------------------------------------------------------------------
|
||||
|
||||
C_SOURCES = ardupilot_SITL_TRICOPTER.c sockets.c sensors.c
|
||||
INCLUDE = -I"./"
|
||||
### Do not modify: this includes Webots global Makefile.include
|
||||
space :=
|
||||
space +=
|
||||
WEBOTS_HOME_PATH=$(subst $(space),\ ,$(strip $(subst \,/,$(WEBOTS_HOME))))
|
||||
include $(WEBOTS_HOME_PATH)/resources/Makefile.include |
Binary file not shown.
@ -0,0 +1,520 @@
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
* File: ardupilot_SITL_TRICOPTER.c |
||||
* Date: 18 Aug 2019 |
||||
* Description: integration with ardupilot SITL simulation. |
||||
* Author: M.S.Hefny (HefnySco) |
||||
* Modifications: |
||||
*/ |
||||
|
||||
|
||||
/*
|
||||
* You may need to add include files like <webots/distance_sensor.h> or |
||||
* <webots/differential_wheels.h>, etc. |
||||
*/ |
||||
#include <math.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <sys/types.h> |
||||
#include <webots/robot.h> |
||||
#include <webots/supervisor.h> |
||||
#include <webots/emitter.h> |
||||
#include "ardupilot_SITL_TRICOPTER.h" |
||||
#include "sockets.h" |
||||
#include "sensors.h" |
||||
|
||||
|
||||
|
||||
#define MOTOR_NUM 3 |
||||
|
||||
static WbDeviceTag motors[MOTOR_NUM]; |
||||
static WbDeviceTag servo; |
||||
static WbDeviceTag gyro; |
||||
static WbDeviceTag accelerometer; |
||||
static WbDeviceTag compass; |
||||
static WbDeviceTag gps; |
||||
static WbDeviceTag camera; |
||||
static WbDeviceTag inertialUnit; |
||||
static WbDeviceTag emitter; |
||||
static WbNodeRef world_info; |
||||
|
||||
static const double *northDirection; |
||||
static double v[MOTOR_NUM]; |
||||
static double servo_value = 0; |
||||
static double servo_value_extra = 0; |
||||
|
||||
int port; |
||||
|
||||
|
||||
static int timestep; |
||||
|
||||
|
||||
#ifdef DEBUG_USE_KB |
||||
/*
|
||||
// Code used tp simulae motors using keys to make sure that sensors directions and motor torques and thrusts are all correct.
|
||||
// You can start this controller and use telnet instead of SITL to start the simulator.
|
||||
Then you can use Keyboard to emulate motor input. |
||||
*/ |
||||
void process_keyboard () |
||||
{ |
||||
switch (wb_keyboard_get_key())
|
||||
{ |
||||
case 'Q': // Q key -> up & left
|
||||
v[0] = 0.0; |
||||
v[1] = 0.0; |
||||
v[2] = 0.0; |
||||
servo_value_extra = 0.0; |
||||
break; |
||||
|
||||
case 'Y': |
||||
v[0] = v[0] + 0.01; |
||||
v[1] = v[1] + 0.01; |
||||
v[2] = v[2] - 0.02; |
||||
break; |
||||
|
||||
case 'H': |
||||
v[0] = v[0] - 0.01; |
||||
v[1] = v[1] - 0.01; |
||||
v[2] = v[2] + 0.02; |
||||
break; |
||||
|
||||
case 'G': |
||||
v[0] = v[0] + 0.01; |
||||
v[1] = v[1] - 0.01; |
||||
break; |
||||
|
||||
case 'J': |
||||
v[0] = v[0] - 0.01; |
||||
v[1] = v[1] + 0.01; |
||||
break; |
||||
|
||||
case 'W': |
||||
for (int i=0; i<MOTOR_NUM;++i) |
||||
{ |
||||
v[i] += 0.01; |
||||
} |
||||
break; |
||||
|
||||
case 'S': |
||||
for (int i=0; i<MOTOR_NUM;++i) |
||||
{ |
||||
v[i] -= 0.01; |
||||
} |
||||
break; |
||||
|
||||
case 'A': |
||||
servo_value_extra = servo_value_extra + 0.01; |
||||
break; |
||||
|
||||
case 'D': |
||||
servo_value_extra = servo_value_extra - 0.01; |
||||
break; |
||||
|
||||
|
||||
} |
||||
|
||||
for (int i=0; i< MOTOR_NUM; ++i) |
||||
{ |
||||
if (v[i] <=0) v[i]=0; |
||||
if (v[i] >=600) v[i]=10; |
||||
|
||||
wb_motor_set_position(motors[i], INFINITY); |
||||
wb_motor_set_velocity(motors[i], v[i]);
|
||||
} |
||||
|
||||
wb_motor_set_position (servo, servo_value_extra); |
||||
wb_motor_set_velocity (servo, 100); |
||||
|
||||
|
||||
printf ("Motors Internal right:%f left:%f back:%f servo:%f\n", v[0],v[1],v[2],servo_value); |
||||
|
||||
} |
||||
#endif |
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
// apply motor thrust.
|
||||
*/ |
||||
void update_controls() |
||||
{ |
||||
/*
|
||||
1 N = 101.9716213 grams force |
||||
Thrust = t1 * |omega| * omega - t2 * |omega| * V |
||||
Where t1 and t2 are the constants specified in the thrustConstants field, |
||||
omega is the motor angular velocity
|
||||
and V is the component of the linear velocity of the center of thrust along the shaft axis. |
||||
|
||||
if Vehicle mass = 1 Kg. and we want omega = 1.0 to hover |
||||
then (mass / 0.10197) / (4 motors) = t1 |
||||
|
||||
LINEAR_THRUST |
||||
we also want throttle to be linear with thrust so we use sqrt to calculate omega from input. |
||||
*/ |
||||
static float factor = 1.0f; |
||||
static float offset = 0.0f; |
||||
static float v[MOTOR_NUM]; |
||||
|
||||
#ifdef LINEAR_THRUST |
||||
v[0] = sqrt(state.motors.w ) * factor + offset; |
||||
v[1] = sqrt(state.motors.x ) * factor + offset; |
||||
v[2] = sqrt(state.motors.z ) * factor + offset; |
||||
#else |
||||
v[0] = (state.motors.w ) * factor + offset; |
||||
v[1] = (state.motors.x ) * factor + offset; |
||||
v[2] = (state.motors.z ) * factor + offset; |
||||
#endif |
||||
|
||||
servo_value = -state.motors.y ; |
||||
|
||||
for ( int i=0; i<MOTOR_NUM; ++i) |
||||
{ |
||||
wb_motor_set_position(motors[i], INFINITY); |
||||
wb_motor_set_velocity(motors[i], v[i]);
|
||||
} |
||||
|
||||
#ifdef DEBUG_USE_KB |
||||
wb_motor_set_position(servo, servo_value + servo_value_extra); |
||||
#else |
||||
wb_motor_set_position(servo, servo_value); |
||||
#endif |
||||
wb_motor_set_velocity(servo, 1000);
|
||||
|
||||
#ifdef DEBUG_MOTORS |
||||
printf ("RAW R:%f L:%f SRV:%f B:%f\n", state.motors.w, state.motors.x, state.motors.y, state.motors.z); |
||||
printf ("Motors R:%f L:%f SRV:%f B:%f\n", v[0], v[1], servo_value, v[2]); |
||||
#endif |
||||
|
||||
|
||||
#ifdef WIND_SIMULATION |
||||
|
||||
double linear_speed = sqrt(linear_velocity[0] * linear_velocity[0] + linear_velocity[1] * linear_velocity[1] + linear_velocity[2] * linear_velocity[2]); |
||||
wind_webots_axis.w = state.wind.w + 0.01 * linear_speed * linear_speed; |
||||
|
||||
if (northDirection[0] == 1) |
||||
{ |
||||
wind_webots_axis.x = state.wind.x - linear_velocity[0]; |
||||
wind_webots_axis.z = -state.wind.y - linear_velocity[2]; // "-state.wind.y" as angle 90 wind is from EAST.
|
||||
wind_webots_axis.y = state.wind.z - linear_velocity[1]; |
||||
} |
||||
else |
||||
{ // as in pyramids and any open map street world.
|
||||
wind_webots_axis.x = state.wind.y - linear_velocity[0]; // always add "linear_velocity" as there is no axis transformation here.
|
||||
wind_webots_axis.z = -state.wind.x - linear_velocity[2]; |
||||
wind_webots_axis.y = state.wind.z - linear_velocity[1]; |
||||
} |
||||
|
||||
wb_emitter_send(emitter, &wind_webots_axis, sizeof(VECTOR4F)); |
||||
|
||||
#endif |
||||
} |
||||
|
||||
|
||||
// data example: [my_controller_SITL] {"engines": [0.000, 0.000, 0.000, 0.000]}
|
||||
// the JSON parser is directly inspired by https://github.com/ArduPilot/ardupilot/blob/master/libraries/SITL/SIM_Morse.cpp
|
||||
bool parse_controls(const char *json) |
||||
{ |
||||
//state.timestamp = 1.0;
|
||||
#ifdef DEBUG_INPUT_DATA |
||||
printf("%s\n", json); |
||||
#endif |
||||
|
||||
for (uint16_t i=0; i < ARRAY_SIZE(keytable); i++) { |
||||
struct keytable *key; |
||||
key = &keytable[i]; |
||||
//printf("search %s/%s\n", key->section, key->key);
|
||||
// look for section header
|
||||
const char *p = strstr(json, key->section); |
||||
if (!p) { |
||||
// we don't have this sensor
|
||||
continue; |
||||
} |
||||
p += strlen(key->section)+1; |
||||
|
||||
// find key inside section
|
||||
p = strstr(p, key->key); |
||||
if (!p) { |
||||
printf("Failed to find key %s/%s\n", key->section, key->key); |
||||
return false; |
||||
} |
||||
|
||||
p += strlen(key->key)+3; |
||||
|
||||
switch (key->type)
|
||||
{ |
||||
case DATA_FLOAT: |
||||
*((float *)key->ptr) = atof(p); |
||||
#ifdef DEBUG_INPUT_DATA |
||||
printf("GOT %s/%s\n", key->section, key->key); |
||||
#endif |
||||
break; |
||||
|
||||
case DATA_DOUBLE: |
||||
*((double *)key->ptr) = atof(p); |
||||
#ifdef DEBUG_INPUT_DATA |
||||
printf("GOT %s/%s\n", key->section, key->key); |
||||
#endif |
||||
break; |
||||
|
||||
case DATA_VECTOR4F: { |
||||
VECTOR4F *v = (VECTOR4F *)key->ptr; |
||||
if (sscanf(p, "[%f, %f, %f, %f]", &(v->w), &(v->x), &(v->y), &(v->z)) != 4) { |
||||
printf("Failed to parse Vector3f for %s %s/%s\n",p, key->section, key->key); |
||||
return false; |
||||
} |
||||
else |
||||
{ |
||||
#ifdef DEBUG_INPUT_DATA |
||||
printf("GOT %s/%s\n[%f, %f, %f, %f]\n ", key->section, key->key,v->w,v->x,v->y,v->z); |
||||
#endif |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
|
||||
void run () |
||||
{ |
||||
|
||||
char send_buf[1000]; //1000 just a safe margin
|
||||
char command_buffer[200]; |
||||
fd_set rfds; |
||||
while (wb_robot_step(timestep) != -1)
|
||||
{ |
||||
#ifdef DEBUG_USE_KB |
||||
process_keyboard(); |
||||
#endif |
||||
|
||||
if (fd == 0)
|
||||
{ |
||||
// if no socket wait till you get a socket
|
||||
fd = socket_accept(sfd); |
||||
if (fd > 0) |
||||
socket_set_non_blocking(fd); |
||||
else if (fd < 0) |
||||
break; |
||||
} |
||||
|
||||
getAllSensors ((char *)send_buf, northDirection, gyro,accelerometer,compass,gps, inertialUnit); |
||||
|
||||
#ifdef DEBUG_SENSORS |
||||
printf("%s\n",send_buf); |
||||
#endif |
||||
|
||||
if (write(fd,send_buf,strlen(send_buf)) <= 0) |
||||
{ |
||||
printf ("Send Data Error\n"); |
||||
} |
||||
|
||||
if (fd)
|
||||
{ |
||||
FD_ZERO(&rfds); |
||||
FD_SET(fd, &rfds); |
||||
struct timeval tv; |
||||
tv.tv_sec = 0.05; |
||||
tv.tv_usec = 0; |
||||
int number = select(fd + 1, &rfds, NULL, NULL, &tv); |
||||
if (number != 0)
|
||||
{
|
||||
// there is a valid connection
|
||||
|
||||
int n = recv(fd, (char *)command_buffer, 200, 0); |
||||
if (n < 0) { |
||||
#ifdef _WIN32 |
||||
int e = WSAGetLastError(); |
||||
if (e == WSAECONNABORTED) |
||||
fprintf(stderr, "Connection aborted.\n"); |
||||
else if (e == WSAECONNRESET) |
||||
fprintf(stderr, "Connection reset.\n"); |
||||
else |
||||
fprintf(stderr, "Error reading from socket: %d.\n", e); |
||||
#else |
||||
if (errno) |
||||
fprintf(stderr, "Error reading from socket: %d.\n", errno); |
||||
#endif |
||||
break; |
||||
} |
||||
if (n==0) |
||||
{ |
||||
break; |
||||
} |
||||
if (command_buffer[0] == 'e') |
||||
{ |
||||
break; |
||||
} |
||||
if (n > 0) |
||||
{ |
||||
|
||||
//printf("Received %d bytes:\n", n);
|
||||
command_buffer[n] = 0; |
||||
parse_controls (command_buffer); |
||||
update_controls(); |
||||
|
||||
|
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
socket_cleanup(); |
||||
} |
||||
|
||||
|
||||
void initialize (int argc, char *argv[]) |
||||
{ |
||||
|
||||
fd_set rfds; |
||||
|
||||
port = 5599; // default port
|
||||
for (int i = 0; i < argc; ++i) |
||||
{ |
||||
if (strcmp (argv[i],"-p")==0) |
||||
{ |
||||
if (argc > i+1 ) |
||||
{ |
||||
port = atoi (argv[i+1]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
sfd = create_socket_server(port); |
||||
|
||||
/* necessary to initialize webots stuff */ |
||||
wb_robot_init(); |
||||
|
||||
// Get WorldInfo Node.
|
||||
WbNodeRef root, node; |
||||
WbFieldRef children, field; |
||||
int n, i; |
||||
root = wb_supervisor_node_get_root(); |
||||
children = wb_supervisor_node_get_field(root, "children"); |
||||
n = wb_supervisor_field_get_count(children); |
||||
printf("This world contains %d nodes:\n", n); |
||||
for (i = 0; i < n; i++) { |
||||
node = wb_supervisor_field_get_mf_node(children, i); |
||||
if (wb_supervisor_node_get_type(node) == WB_NODE_WORLD_INFO) |
||||
{ |
||||
world_info = node;
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
printf("\n"); |
||||
node = wb_supervisor_field_get_mf_node(children, 0); |
||||
field = wb_supervisor_node_get_field(node, "northDirection"); |
||||
northDirection = wb_supervisor_field_get_sf_vec3f(field); |
||||
|
||||
if (northDirection[0] == 1) |
||||
{ |
||||
printf ("Axis Default Directions"); |
||||
} |
||||
|
||||
printf("WorldInfo.northDirection = %g %g %g\n\n", northDirection[0], northDirection[1], northDirection[2]); |
||||
|
||||
|
||||
// get Self Node
|
||||
self_node = wb_supervisor_node_get_self(); |
||||
|
||||
|
||||
// keybaard
|
||||
timestep = (int)wb_robot_get_basic_time_step(); |
||||
wb_keyboard_enable(timestep); |
||||
|
||||
|
||||
|
||||
// inertialUnit
|
||||
inertialUnit = wb_robot_get_device("inertial_unit"); |
||||
wb_inertial_unit_enable(inertialUnit, timestep); |
||||
|
||||
// gyro
|
||||
gyro = wb_robot_get_device("gyro1"); |
||||
wb_gyro_enable(gyro, timestep); |
||||
|
||||
// accelerometer
|
||||
accelerometer = wb_robot_get_device("accelerometer1"); |
||||
wb_accelerometer_enable(accelerometer, timestep); |
||||
|
||||
// compass
|
||||
compass = wb_robot_get_device("compass1"); |
||||
wb_compass_enable(compass, timestep); |
||||
|
||||
// gps
|
||||
gps = wb_robot_get_device("gps1"); |
||||
wb_gps_enable(gps, timestep); |
||||
|
||||
// camera
|
||||
camera = wb_robot_get_device("camera1"); |
||||
wb_camera_enable(camera, timestep); |
||||
|
||||
#ifdef WIND_SIMULATION |
||||
// emitter
|
||||
emitter = wb_robot_get_device("emitter_plugin"); |
||||
#endif |
||||
|
||||
const char *MOTOR_NAMES[] = {"motor1", "motor2", "motor3"}; |
||||
|
||||
// get motor device tags
|
||||
for (i = 0; i < MOTOR_NUM; i++) { |
||||
motors[i] = wb_robot_get_device(MOTOR_NAMES[i]); |
||||
v[i] = 0.0f; |
||||
//assert(motors[i]);
|
||||
} |
||||
|
||||
servo = wb_robot_get_device("servo_tail"); |
||||
|
||||
FD_ZERO(&rfds); |
||||
FD_SET(sfd, &rfds); |
||||
} |
||||
/*
|
||||
* This is the main program. |
||||
* The arguments of the main function can be specified by the |
||||
* "controllerArgs" field of the Robot node |
||||
*/ |
||||
int main(int argc, char **argv) |
||||
{ |
||||
|
||||
|
||||
|
||||
initialize( argc, argv); |
||||
|
||||
/*
|
||||
* You should declare here WbDeviceTag variables for storing |
||||
* robot devices like this: |
||||
* WbDeviceTag my_sensor = wb_robot_get_device("my_sensor"); |
||||
* WbDeviceTag my_actuator = wb_robot_get_device("my_actuator"); |
||||
*/ |
||||
|
||||
/* main loop
|
||||
* Perform simulation steps of TIME_STEP milliseconds |
||||
* and leave the loop when the simulation is over |
||||
*/ |
||||
|
||||
|
||||
/*
|
||||
* Read the sensors : |
||||
* Enter here functions to read sensor data, like: |
||||
* double val = wb_distance_sensor_get_value(my_sensor); |
||||
*/ |
||||
|
||||
/* Process sensor data here */ |
||||
|
||||
/*
|
||||
* Enter here functions to send actuator commands, like: |
||||
* wb_differential_wheels_set_speed(100.0,100.0); |
||||
*/ |
||||
run(); |
||||
|
||||
|
||||
/* Enter your cleanup code here */ |
||||
|
||||
/* This is necessary to cleanup webots resources */ |
||||
wb_robot_cleanup(); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
//#define DEBUG_MOTORS
|
||||
// #define DEBUG_SENSORS
|
||||
//#define DEBUG_USE_KB
|
||||
//#define DEBUG_INPUT_DATA
|
||||
// #define LINEAR_THRUST
|
||||
//#define WIND_SIMULATION
|
||||
|
||||
|
||||
#define ARRAY_SIZE(_arr) (sizeof(_arr) / sizeof(_arr[0])) |
||||
|
||||
|
||||
enum data_type { |
||||
DATA_FLOAT, |
||||
DATA_DOUBLE, |
||||
DATA_VECTOR4F, |
||||
}; |
||||
|
||||
struct vector4f
|
||||
{ |
||||
float w,x,y,z; |
||||
}; |
||||
|
||||
|
||||
typedef struct vector4f VECTOR4F; |
||||
|
||||
struct { |
||||
double timestamp; |
||||
VECTOR4F motors; |
||||
VECTOR4F wind;
|
||||
/*
|
||||
struct { |
||||
float speed; // m/s
|
||||
float direction; // degrees 0..360
|
||||
float turbulence; |
||||
float dir_z; //degrees -90..90
|
||||
} wind; |
||||
*/ |
||||
} state, last_state; |
||||
|
||||
|
||||
|
||||
// table to aid parsing of JSON sensor data
|
||||
struct keytable { |
||||
const char *section; |
||||
const char *key; |
||||
void *ptr; |
||||
enum data_type type; |
||||
|
||||
} keytable[2] = { |
||||
//{ "", "timestamp", &state.timestamp, DATA_DOUBLE },
|
||||
{ "", "eng", &state.motors, DATA_VECTOR4F }, /*right,left,servo,back*/ |
||||
{ "", "wnd", &state.wind, DATA_VECTOR4F } /*speed,x,y,z in Ardupilot AXIS*/
|
||||
}; |
||||
|
||||
|
||||
/*
|
||||
w: wind speed |
||||
x , y, z: wind direction. |
||||
*/ |
||||
VECTOR4F __attribute__((packed, aligned(1))) wind_webots_axis; |
||||
|
@ -0,0 +1,194 @@
@@ -0,0 +1,194 @@
|
||||
#include <stdio.h> |
||||
#include <sys/time.h> |
||||
#include <webots/supervisor.h> |
||||
#include "sensors.h" |
||||
|
||||
#define M_PI 3.14159265358979323846 |
||||
#define M_PI2 6.28318530718 |
||||
|
||||
|
||||
/*
|
||||
https://discuss.ardupilot.org/t/copter-x-y-z-which-is-which/6823/2
|
||||
|
||||
Copy pasted what’s important: |
||||
NED Coordinate System: |
||||
|
||||
The x axis is aligned with the vector to the north pole (tangent to meridians). |
||||
The y axis points to the east side (tangent to parallels) |
||||
The z axis points to the center of the earth |
||||
There is also Body Fixed Frame: |
||||
Body Fixed Frame (Attached to the aircraft) |
||||
|
||||
The x axis points in forward (defined by geometry and not by movement) direction. (= roll axis) |
||||
The y axis points to the right (geometrically) (= pitch axis) |
||||
The z axis points downwards (geometrically) (= yaw axis) |
||||
In order to convert from Body Frame to NED what you need to call this function: |
||||
|
||||
copter.rotate_body_frame_to_NE(vel_vector.x, vel_vector.y); |
||||
|
||||
|
||||
|
||||
|
||||
*/ |
||||
/*
|
||||
returns: "yaw":_6.594794831471518e-05,"pitch":_-0.0005172680830582976,"roll":_0.022908752784132957}} |
||||
*/ |
||||
void getInertia (const WbDeviceTag inertialUnit, const double *northDirection, char *buf) |
||||
{ |
||||
const double *inertial_directions = wb_inertial_unit_get_roll_pitch_yaw (inertialUnit); |
||||
if (northDirection[0] == 1) |
||||
{ |
||||
sprintf(buf,"\"roll\": %f,\"pitch\": %f,\"yaw\": %f",inertial_directions[0], inertial_directions[1], inertial_directions[2]); |
||||
} |
||||
else |
||||
{ |
||||
sprintf(buf,"\"roll\": %f,\"pitch\": %f,\"yaw\": %f",inertial_directions[1], -inertial_directions[0], inertial_directions[2]); |
||||
} |
||||
|
||||
return ; |
||||
} |
||||
|
||||
/*
|
||||
returns: "magnetic_field":_[23088.669921875,_3876.001220703125,_-53204.57421875] |
||||
*/ |
||||
void getCompass (const WbDeviceTag compass, const double *northDirection, char *buf) |
||||
{ |
||||
const double *north3D = wb_compass_get_values(compass); |
||||
if (northDirection[0] == 1) |
||||
{ |
||||
sprintf(buf,"[%f, %f, %f]",north3D[0], north3D[2], north3D[1]); |
||||
} |
||||
else |
||||
{ |
||||
sprintf(buf,"[%f, %f, %f]",north3D[2], -north3D[0], north3D[1]); |
||||
} |
||||
|
||||
return ; |
||||
} |
||||
|
||||
|
||||
|
||||
/*
|
||||
returns: "vehicle.gps":{"timestamp":_1563301031.055164,"x":_5.5127296946011484e-05,"y":_-0.0010968948481604457,"z":_0.037179552018642426},
|
||||
*/ |
||||
void getGPS (const WbDeviceTag gps, const double *northDirection, char *buf) |
||||
{ |
||||
|
||||
const double *north3D = wb_gps_get_values(gps); |
||||
if (northDirection[0] == 1) |
||||
{ |
||||
sprintf(buf,"\"x\": %f,\"y\": %f,\"z\": %f", north3D[0], north3D[2], north3D[1]); |
||||
} |
||||
else |
||||
{ |
||||
sprintf(buf,"\"x\": %f,\"y\": %f,\"z\": %f", north3D[2], -north3D[0], north3D[1]); |
||||
} |
||||
|
||||
|
||||
return ; |
||||
} |
||||
|
||||
/*
|
||||
returns: "linear_acceleration": [0.005074390675872564, 0.22471477091312408, 9.80740737915039] |
||||
*/ |
||||
void getAcc (const WbDeviceTag accelerometer, const double *northDirection, char *buf) |
||||
{ |
||||
//SHOULD BE CORRECT
|
||||
const double *a = wb_accelerometer_get_values(accelerometer); |
||||
if (northDirection[0] == 1) |
||||
{ |
||||
sprintf(buf,"[%f, %f, %f]",a[0], a[2], a[1]); |
||||
} |
||||
else |
||||
{ |
||||
sprintf(buf,"[%f, %f, %f]",a[0], a[2], a[1]); |
||||
} |
||||
|
||||
|
||||
//sprintf(buf,"[0.0, 0.0, 0.0]");
|
||||
|
||||
return ; |
||||
} |
||||
|
||||
|
||||
/*
|
||||
returns: "angular_velocity": [-1.0255117643964695e-07, -8.877226775894087e-08, 2.087078510015772e-09] |
||||
*/ |
||||
void getGyro (const WbDeviceTag gyro, const double *northDirection, char *buf) |
||||
{ |
||||
|
||||
const double *g = wb_gyro_get_values(gyro); |
||||
if (northDirection[0] == 1) |
||||
{ |
||||
sprintf(buf,"[%f, %f, %f]",g[0], g[2], g[1]); |
||||
} |
||||
else |
||||
{ |
||||
sprintf(buf,"[%f, %f, %f]",g[0], g[2], g[1]); |
||||
} |
||||
|
||||
return ; |
||||
} |
||||
|
||||
|
||||
void getLinearVelocity (WbNodeRef nodeRef, const double *northDirection, char * buf) |
||||
{ |
||||
linear_velocity = wb_supervisor_node_get_velocity (nodeRef); |
||||
if (linear_velocity != NULL) |
||||
{ |
||||
if (northDirection[0] == 1) |
||||
{ |
||||
sprintf (buf,"[%f, %f, %f]", linear_velocity[0], linear_velocity[2], linear_velocity[1]); |
||||
} |
||||
else |
||||
{ |
||||
sprintf (buf,"[%f, %f, %f]", linear_velocity[2], -linear_velocity[0], linear_velocity[1]); |
||||
}
|
||||
} |
||||
|
||||
} |
||||
|
||||
void getAllSensors (char *buf, const double *northDirection, WbDeviceTag gyro, WbDeviceTag accelerometer, WbDeviceTag compass, const WbDeviceTag gps, WbDeviceTag inertial_unit) |
||||
{ |
||||
|
||||
/*
|
||||
{"timestamp": 1563544049.2840538,
|
||||
"vehicle.imu": {"timestamp": 1563544049.2673872,
|
||||
"angular_velocity": [-2.0466000023589004e-06, 3.1428675129063777e-07, -6.141597896913709e-09], |
||||
"linear_acceleration": [0.005077465437352657, 0.22471386194229126, 9.807389259338379],
|
||||
"magnetic_field": [23088.71484375, 3875.498046875, -53204.48046875]},
|
||||
"vehicle.gps": { |
||||
"timestamp": 1563544049.2673872,
|
||||
"x": 4.985610576113686e-05, "y": -0.0010973707539960742, "z": 0.037179529666900635},
|
||||
"vehicle.velocity": {"timestamp": 1563544049.2673872,
|
||||
"linear_velocity": [-3.12359499377024e-10, -1.3824124955874595e-08, -6.033386625858839e-07], |
||||
"angular_velocity": [-2.0466000023589004e-06, 3.1428675129063777e-07, -6.141597896913709e-09],
|
||||
"world_linear_velocity": [0.0, 0.0, -6.034970851942489e-07]},
|
||||
"vehicle.pose": {"timestamp": 1563544049.2673872,
|
||||
"x": 4.985610576113686e-05, "y": -0.0010973707539960742, "z": 0.037179529666900635, "yaw": 8.899446402210742e-05, "pitch": -0.0005175824626348913, "roll": 0.022908702492713928} |
||||
} |
||||
*/ |
||||
|
||||
|
||||
static char compass_buf [150]; |
||||
static char acc_buf [150]; |
||||
static char gyro_buf [150]; |
||||
static char gps_buf [150]; |
||||
static char inertial_buf [150]; |
||||
static char linear_velocity_buf [150]; |
||||
|
||||
char szTime[21]; |
||||
double time = wb_robot_get_time(); // current simulation time in [s]
|
||||
sprintf(szTime,"%f", time); |
||||
|
||||
getGyro(gyro, northDirection, gyro_buf); |
||||
getAcc(accelerometer, northDirection, acc_buf); |
||||
getCompass(compass, northDirection, compass_buf); |
||||
getGPS(gps, northDirection, gps_buf); |
||||
getInertia (inertial_unit, northDirection, inertial_buf); |
||||
getLinearVelocity(self_node, northDirection, linear_velocity_buf); |
||||
|
||||
sprintf (buf,"{\"ts\": %s,\"vehicle.imu\": {\"av\": %s,\"la\": %s,\"mf\": %s,\"vehicle.gps\":{%s},\"vehicle.velocity\":{\"wlv\": %s},\"vehicle.pose\":{%s,%s}}\r\n" |
||||
, szTime, gyro_buf, acc_buf, compass_buf, gps_buf, linear_velocity_buf, gps_buf, inertial_buf ); |
||||
|
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
|
||||
|
||||
#include <webots/robot.h> |
||||
#include <webots/keyboard.h> |
||||
#include <webots/compass.h> |
||||
#include <webots/accelerometer.h> |
||||
#include <webots/inertial_unit.h> |
||||
#include <webots/gps.h> |
||||
#include <webots/gyro.h> |
||||
#include <webots/motor.h> |
||||
#include <webots/camera.h> |
||||
|
||||
|
||||
WbNodeRef self_node; |
||||
double *linear_velocity; |
||||
|
||||
void getInertia (const WbDeviceTag inertialUnit, const double *northDirection, char *buf); |
||||
void getLinearVelocity (WbNodeRef nodeRef, const double *northDirection, char * buf); |
||||
void getCompass (const WbDeviceTag compass, const double *northDirection, char *buf); |
||||
void getAcc (const WbDeviceTag accelerometer, const double *northDirection, char *buf); |
||||
void getGyro (const WbDeviceTag gyro, const double *northDirection, char *buf); |
||||
void getGPS (const WbDeviceTag gps, const double *northDirection, char *buf); |
||||
void getAllSensors (char *buf, const double *northDirection, WbDeviceTag gyro, WbDeviceTag accelerometer, WbDeviceTag compass, const WbDeviceTag gps, WbDeviceTag inertial_unit); |
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
|
||||
#include "sockets.h" |
||||
|
||||
|
||||
bool socket_init() { |
||||
#ifdef _WIN32 /* initialize the socket API */ |
||||
WSADATA info; |
||||
if (WSAStartup(MAKEWORD(1, 1), &info) != 0) { |
||||
fprintf(stderr, "Cannot initialize Winsock.\n"); |
||||
return false; |
||||
} |
||||
#endif |
||||
return true; |
||||
} |
||||
|
||||
bool socket_set_non_blocking(int fd) { |
||||
if (fd < 0) |
||||
return false; |
||||
#ifdef _WIN32 |
||||
unsigned long mode = 1; |
||||
return (ioctlsocket(fd, FIONBIO, &mode) == 0) ? true : false; |
||||
#else |
||||
int flags = fcntl(fd, F_GETFL, 0) | O_NONBLOCK; |
||||
return (fcntl(fd, F_SETFL, flags) == 0) ? true : false; |
||||
#endif |
||||
} |
||||
|
||||
int socket_accept(int server_fd) { |
||||
int cfd; |
||||
struct sockaddr_in client; |
||||
struct hostent *client_info; |
||||
#ifndef _WIN32 |
||||
socklen_t asize; |
||||
#else |
||||
int asize; |
||||
#endif |
||||
asize = sizeof(struct sockaddr_in); |
||||
cfd = accept(server_fd, (struct sockaddr *)&client, &asize); |
||||
if (cfd == -1) { |
||||
#ifdef _WIN32 |
||||
int e = WSAGetLastError(); |
||||
if (e == WSAEWOULDBLOCK) |
||||
return 0; |
||||
fprintf(stderr, "Accept error: %d.\n", e); |
||||
#else |
||||
if (errno == EWOULDBLOCK) |
||||
return 0; |
||||
fprintf(stderr, "Accept error: %d.\n", errno); |
||||
#endif |
||||
return -1; |
||||
} |
||||
client_info = gethostbyname((char *)inet_ntoa(client.sin_addr)); |
||||
printf("Accepted connection from: %s.\n", client_info->h_name); |
||||
return cfd; |
||||
} |
||||
|
||||
bool socket_close(int fd) { |
||||
#ifdef _WIN32 |
||||
return (closesocket(fd) == 0) ? true : false; |
||||
#else |
||||
return (close(fd) == 0) ? true : false; |
||||
#endif |
||||
} |
||||
|
||||
bool socket_cleanup() { |
||||
#ifdef _WIN32 |
||||
return (WSACleanup() == 0) ? true : false; |
||||
#else |
||||
return true; |
||||
#endif |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Creates a socket and bind it to port. |
||||
*/ |
||||
int create_socket_server(int port) { |
||||
int sfd, rc; |
||||
struct sockaddr_in address; |
||||
if (!socket_init()) |
||||
{ |
||||
fprintf (stderr, "socket_init failed"); |
||||
return -1; |
||||
} |
||||
sfd = socket(AF_INET, SOCK_STREAM, 0); |
||||
if (sfd == -1) { |
||||
fprintf(stderr, "Cannot create socket.\n"); |
||||
return -1; |
||||
} |
||||
int one = 1; |
||||
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
||||
memset(&address, 0, sizeof(struct sockaddr_in)); |
||||
address.sin_family = AF_INET; |
||||
address.sin_port = htons((unsigned short)port); |
||||
address.sin_addr.s_addr = INADDR_ANY; |
||||
rc = bind(sfd, (struct sockaddr *)&address, sizeof(struct sockaddr)); |
||||
if (rc == -1) { |
||||
fprintf(stderr, "Cannot bind port %d.\n", port); |
||||
socket_close(sfd); |
||||
return -1; |
||||
} |
||||
if (listen(sfd, 1) == -1) { |
||||
fprintf(stderr, "Cannot listen for connections.\n"); |
||||
socket_close(sfd); |
||||
return -1; |
||||
} |
||||
|
||||
printf ("socket initialized at port %d.\n", port); |
||||
return sfd; |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
|
||||
#include <stdbool.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <sys/types.h> |
||||
|
||||
|
||||
#ifdef _WIN32 |
||||
#include <winsock.h> |
||||
#else |
||||
#include <arpa/inet.h> /* definition of inet_ntoa */ |
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <netdb.h> /* definition of gethostbyname */ |
||||
#include <netinet/in.h> /* definition of struct sockaddr_in */ |
||||
#include <stdlib.h> |
||||
#include <sys/socket.h> |
||||
#include <sys/time.h> |
||||
#include <unistd.h> /* definition of close */ |
||||
#endif |
||||
|
||||
int create_socket_server(int port); |
||||
bool socket_cleanup(); |
||||
int socket_accept(int server_fd); |
||||
bool socket_set_non_blocking(int fd); |
||||
|
||||
int fd; |
||||
int sfd; |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash |
||||
|
||||
# assume we start the script from the root directory |
||||
ROOTDIR=$PWD |
||||
$PWD/Tools/autotest/sim_vehicle.py -v ArduCopter -w --model webots-tri --add-param-file=libraries/SITL/examples/Webots/tricopter.parm |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
FRAME_CLASS 7.000000 |
||||
FRAME_TYPE 0.000000 |
||||
ATC_ANG_PIT_P 1.0 |
||||
ATC_ANG_RLL_P 1.0 |
||||
ATC_ANG_YAW_P 0.5 |
||||
ATC_RAT_PIT_P 3.5 |
||||
ATC_RAT_RLL_P 3.5 |
||||
ATC_RAT_YAW_P 1.5 |
||||
ATC_RAT_YAW_I 0.03 |
||||
ATC_RAT_YAW_IMAX 0.50000 |
||||
SIM_WIND_DIR 90.0 |
||||
SIM_WIND_SPD 0.0 |
||||
SIM_WIND_TURB 0.0 |
@ -0,0 +1,286 @@
@@ -0,0 +1,286 @@
|
||||
#VRML_SIM R2019b utf8 |
||||
WorldInfo { |
||||
gravity 0 -9.80665 0 |
||||
physics "sitl_physics_env" |
||||
basicTimeStep 2 |
||||
FPS 25 |
||||
optimalThreadCount 4 |
||||
randomSeed 52 |
||||
} |
||||
DogHouse { |
||||
translation 34.82 0.76 -24.56 |
||||
name "dog house(1)" |
||||
} |
||||
DogHouse { |
||||
translation 161.819 0.75 -152.174 |
||||
name "dog house(2)" |
||||
} |
||||
DogHouse { |
||||
translation 185.42 0.77 48.97 |
||||
name "dog house(5)" |
||||
} |
||||
Viewpoint { |
||||
orientation 0.9997750421775041 -0.014997750480821456 -0.01499775048082146 4.712163997257285 |
||||
position 0.17712971811531414 14.83048200793912 -1.4201693307676047 |
||||
follow "tricopter" |
||||
} |
||||
Background { |
||||
skyColor [ |
||||
0.15 0.5 1 |
||||
] |
||||
cubemap Cubemap { |
||||
} |
||||
} |
||||
Solid { |
||||
translation 36.93 0.77 -37.93 |
||||
children [ |
||||
HouseWithGarage { |
||||
} |
||||
] |
||||
} |
||||
Solid { |
||||
translation 192.76999999999998 0 64.98 |
||||
rotation 0 1 0 -1.5707963071795863 |
||||
children [ |
||||
HouseWithGarage { |
||||
} |
||||
] |
||||
name "solid(1)" |
||||
} |
||||
DEF DEF_VEHICLE Robot { |
||||
translation -8.233889875751989e-05 0.666500515499142 -1.3598750857814472 |
||||
rotation 0.00514799893982893 0.9999767940663002 0.004461999081102697 0.261804 |
||||
children [ |
||||
Compass { |
||||
name "compass1" |
||||
} |
||||
Camera { |
||||
translation 0 0.25 0 |
||||
name "camera1" |
||||
} |
||||
Transform { |
||||
translation -0.34 0 0 |
||||
rotation 0 1 0 1.5707959999999999 |
||||
children [ |
||||
HingeJoint { |
||||
jointParameters HingeJointParameters { |
||||
position -9.388038782122357e-12 |
||||
axis 0 0 1 |
||||
} |
||||
device [ |
||||
RotationalMotor { |
||||
name "servo_tail" |
||||
maxVelocity 50000 |
||||
maxTorque 1000 |
||||
} |
||||
] |
||||
endPoint Solid { |
||||
translation -4.884982810326628e-15 -4.1022629410960365e-12 1.8091922030330322e-13 |
||||
rotation 2.3470124341273664e-11 1 -3.708275057969111e-10 1.5707963071795863 |
||||
children [ |
||||
Propeller { |
||||
shaftAxis 0 1 0 |
||||
thrustConstants 11.44 0 |
||||
torqueConstants 1e-05 0 |
||||
device RotationalMotor { |
||||
name "motor3" |
||||
controlPID 10.001 0 0 |
||||
maxVelocity 1000 |
||||
} |
||||
fastHelix Solid { |
||||
children [ |
||||
Shape { |
||||
appearance Appearance { |
||||
material Material { |
||||
diffuseColor 1 0 0.1 |
||||
} |
||||
} |
||||
geometry Cylinder { |
||||
height 0.002 |
||||
radius 0.02 |
||||
} |
||||
} |
||||
] |
||||
} |
||||
slowHelix Solid { |
||||
rotation 0 1 0 2.238367478129037 |
||||
children [ |
||||
Shape { |
||||
appearance Appearance { |
||||
material Material { |
||||
diffuseColor 0 1 0.09999999999999999 |
||||
} |
||||
} |
||||
geometry Cylinder { |
||||
height 0.002 |
||||
radius 0.02 |
||||
} |
||||
} |
||||
] |
||||
} |
||||
} |
||||
] |
||||
name "solid(1)" |
||||
boundingObject Box { |
||||
size 0.01 0.01 0.01 |
||||
} |
||||
physics Physics { |
||||
mass 0.001 |
||||
} |
||||
} |
||||
} |
||||
] |
||||
} |
||||
Transform { |
||||
translation 0.17 0 0.3 |
||||
children [ |
||||
Propeller { |
||||
shaftAxis 0 -1 0 |
||||
thrustConstants -11.443 0 |
||||
torqueConstants 1e-05 0 |
||||
device RotationalMotor { |
||||
name "motor1" |
||||
controlPID 10.001 0 0 |
||||
maxVelocity 1000 |
||||
} |
||||
fastHelix Solid { |
||||
children [ |
||||
Shape { |
||||
appearance Appearance { |
||||
material Material { |
||||
diffuseColor 1 0 0.1 |
||||
} |
||||
} |
||||
geometry Cylinder { |
||||
height 0.002 |
||||
radius 0.02 |
||||
} |
||||
} |
||||
] |
||||
} |
||||
slowHelix Solid { |
||||
rotation 0 1 0 0.012993 |
||||
children [ |
||||
Shape { |
||||
appearance Appearance { |
||||
material Material { |
||||
diffuseColor 1 0 0.1 |
||||
} |
||||
} |
||||
geometry Cylinder { |
||||
height 0.002 |
||||
radius 0.02 |
||||
} |
||||
} |
||||
] |
||||
} |
||||
} |
||||
] |
||||
} |
||||
Transform { |
||||
translation 0.16 0 -0.3 |
||||
children [ |
||||
Propeller { |
||||
shaftAxis 0 1 0 |
||||
thrustConstants 11.443 0 |
||||
torqueConstants 1e-05 0 |
||||
device RotationalMotor { |
||||
name "motor2" |
||||
controlPID 10.001 0 0 |
||||
maxVelocity 1000 |
||||
} |
||||
fastHelix Solid { |
||||
children [ |
||||
Shape { |
||||
appearance Appearance { |
||||
material Material { |
||||
diffuseColor 1 0 0.1 |
||||
} |
||||
} |
||||
geometry Cylinder { |
||||
height 0.002 |
||||
radius 0.02 |
||||
} |
||||
} |
||||
] |
||||
} |
||||
slowHelix Solid { |
||||
rotation 0 1 0 0.012993 |
||||
children [ |
||||
Shape { |
||||
appearance Appearance { |
||||
material Material { |
||||
diffuseColor 1 0 0.1 |
||||
} |
||||
} |
||||
geometry Cylinder { |
||||
height 0.002 |
||||
radius 0.02 |
||||
} |
||||
} |
||||
] |
||||
} |
||||
} |
||||
] |
||||
} |
||||
Emitter { |
||||
rotation 0 1 0 -1.5707963071795863 |
||||
name "emitter_plugin" |
||||
} |
||||
InertialUnit { |
||||
name "inertial_unit" |
||||
} |
||||
Gyro { |
||||
name "gyro1" |
||||
} |
||||
Accelerometer { |
||||
name "accelerometer1" |
||||
} |
||||
GPS { |
||||
name "gps1" |
||||
} |
||||
Solid { |
||||
children [ |
||||
Shape { |
||||
appearance Appearance { |
||||
material Material { |
||||
} |
||||
} |
||||
geometry Box { |
||||
size 0.1 0.1 0.1 |
||||
} |
||||
} |
||||
] |
||||
boundingObject Box { |
||||
size 0.1 0.1 0.1 |
||||
} |
||||
physics Physics { |
||||
mass 0.6 |
||||
} |
||||
} |
||||
] |
||||
name "tricopter" |
||||
physics Physics { |
||||
mass 0.001 |
||||
} |
||||
controller "ardupilot_SITL_TRICOPTER" |
||||
supervisor TRUE |
||||
} |
||||
DirectionalLight { |
||||
direction 0 -1 0 |
||||
} |
||||
UnevenTerrain { |
||||
size 500 1 500 |
||||
} |
||||
HouseWithGarage { |
||||
translation 174.25 1.88 -157.5 |
||||
rotation 0 1 0 -1.5707963071795863 |
||||
} |
||||
AdvertisingBoard { |
||||
translation 0 2.35 -5.71 |
||||
} |
||||
AdvertisingBoard { |
||||
translation 84.03999999999999 2.35 -5.81 |
||||
rotation 0 1 0 -1.5707963071795863 |
||||
name "advertising board(1)" |
||||
} |
Loading…
Reference in new issue