MoffKalast / vizanti

A mission planner and visualizer for controlling outdoor ROS robots.
https://wiki.ros.org/vizanti
BSD 3-Clause "New" or "Revised" License
133 stars 25 forks source link

Changing the Default Layout #60

Open gxomalis opened 4 months ago

gxomalis commented 4 months ago

Hello @MoffKalast

Vizanti has been a great tool for the project I'm working on. I have setup the layout the way I want with the widgets I need, but if I open Vizanti on a new browser, the layout goes back to the default one and I have to load my layout file or re-add the widgets.

How can I change the Default Layout to the one I want to use, so it loads automatically the widgets I want when I'm on a new browser?

Here's the layout I'm using now: robot_config.json

MoffKalast commented 4 months ago

Hey glad you like it, I think you could achieve that by modifying the persistent.js module (or here for ros2).

Initially if there's no saved layout it'll set itself to a blank-ish setup manually, but you could instead have it run this.fromJSON on a JSON string of that layout you've got.

Simplest edit would be to just embed it as a string def like so (it might need a JSON.parse between the string and fromJSON, I haven't tested it):

const defaultString = "{\"navbar\":[{\"type\":\"settings\",\"id\":\"settings_default\"},{\"type\":\"rosbridge\",\"id\":\"rosbridge_default\"},{\"type\":\"grid\",\"id\":\"grid_default\"},{\"type\":\"tf\",\"id\":\"tf_default\"},{\"type\":\"map\",\"id\":\"map_autoID_0\"},{\"type\":\"simplegoal\",\"id\":\"simplegoal_autoID_1\"},{\"type\":\"path\",\"id\":\"path_autoID_2\"},{\"type\":\"teleop\",\"id\":\"teleop_autoID_4\"},{\"type\":\"scan\",\"id\":\"scan_autoID_5\"},{\"type\":\"robotmodel\",\"id\":\"robotmodel_autoID_6\"}],\"view\":{\"center\":{\"x\":2.705681638721338,\"y\":0.5385767082942933},\"scale\":46.10223738657773},\"grid_default\":{\"size\":1,\"thickness\":1,\"colour\":\"#3e556a\"},\"tf_default\":{\"show_names\":true,\"show_axes\":true,\"show_lines\":true,\"scale\":1,\"frame_visibility\":{\"base_link\":true,\"odom\":true,\"lower\":true,\"LDS-01\":true,\"solid\":true,\"leftwheel\":true,\"rightwheel\":true,\"map\":true,\"base_footprint\":true,\"screw0\":true,\"screw1\":true,\"screw2\":true,\"screw3\":true,\"accelerometer\":true,\"compass\":true,\"gps\":true,\"gyro\":true,\"imu_link\":true,\"inertial_unit\":true,\"wheel_left_link\":true,\"wheel_right_link\":true,\"camera_link\":true,\"camera_rgb_frame\":true,\"camera_rgb_optical_frame\":true,\"caster_back_left_link\":true,\"caster_back_right_link\":true,\"base_scan\":true}},\"settings_default\":{\"fixed_frame\":\"map\",\"background_color\":\"#273444\"},\"uid\":15,\"map_autoID_0\":{\"topic\":\"/map\",\"opacity\":\"0.65\",\"costmap_mode\":false,\"throttle\":\"1000\",\"use_timestamp\":false},\"simplegoal_autoID_1\":{\"topic\":\"/goal_pose\"},\"path_autoID_2\":{\"topic\":\"/local_plan\",\"color\":\"#15ff00\"},\"posewithcovariancestamped_autoID_3\":{\"topic\":\"/amcl_pose\",\"scale\":1},\"teleop_autoID_4\":{\"topic\":\"/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"87.26287262872629%\",\"joy_offset_y\":\"84%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"path_autoID_5\":{\"topic\":\"/local_plan\",\"color\":\"#5ed753\"},\"waypoints_autoID_6\":{\"topic\":\"/plan\",\"fixed_frame\":\"\",\"points\":[],\"start_closest\":true},\"path_autoID_7\":{\"topic\":\"/plan\",\"color\":\"#5ed753\"},\"path_autoID_8\":{\"topic\":\"/plan\",\"color\":\"#5ed753\"},\"path_autoID_9\":{\"topic\":\"/plan_smoothed\",\"color\":\"#5ed753\"},\"path_autoID_10\":{\"topic\":\"/received_global_plan\",\"color\":\"#5ed753\"},\"path_autoID_11\":{\"topic\":\"/transformed_global_plan\",\"color\":\"#5ed753\"},\"path_autoID_12\":{\"topic\":\"/plan\",\"color\":\"#5ed753\"},\"waypoints_autoID_13\":{\"topic\":\"/plan\",\"fixed_frame\":\"map\",\"points\":[],\"start_closest\":true},\"map_autoID_14\":{\"topic\":\"/map\",\"opacity\":\"0.7\",\"costmap_mode\":false,\"throttle\":\"1000\",\"use_timestamp\":false},\"path_autoID_15\":{\"topic\":\"/local_plan\",\"color\":\"#5ed753\"},\"scan_autoID_5\":{\"topic\":\"/scan\",\"opacity\":\"0.5\",\"thickness\":\"0.07\",\"color\":\"#ff1100\",\"throttle\":\"2000\"},\"robotmodel_autoID_6\":{\"frame\":\"base_link\",\"sprite\":\"pancake\",\"length\":\"0.5\"},\"simplegoal_autoID_7\":{\"topic\":\"/goal_pose\"},\"button_autoID_8\":{\"topic\":\"\",\"text\":\"Text\",\"typedict\":{}},\"teleop_autoID_9\":{\"topic\":\"/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"50%\",\"joy_offset_y\":\"85%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"initialpose_autoID_10\":{\"topic\":\"/initialpose\"},\"robotmodel_autoID_7\":{\"frame\":\"base_link\",\"sprite\":\"4wd\",\"length\":\"0.5\"},\"robotmodel_autoID_8\":{\"frame\":\"base_link\",\"sprite\":\"4wd\",\"length\":\"0.5\"},\"tf_autoID_9\":{\"show_names\":true,\"show_axes\":true,\"show_lines\":true,\"scale\":1,\"frame_visibility\":{}},\"teleop_autoID_10\":{\"topic\":\"/turtle1/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"83.33333333333334%\",\"joy_offset_y\":\"85.16666666666667%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"teleop_autoID_11\":{\"topic\":\"/turtle1/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"50%\",\"joy_offset_y\":\"85%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"teleop_autoID_12\":{\"topic\":\"/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"50%\",\"joy_offset_y\":\"85%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"teleop_autoID_13\":{\"topic\":\"/turtle1/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"50%\",\"joy_offset_y\":\"85%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"markerarray_autoID_14\":{\"topic\":\"/marker\"}}";

...

if (localStorage.hasOwnProperty("settings")) {
    this.fromJSON(localStorage.getItem("settings"));
}else{
    this.fromJSON(defaultString);
}

A more proper solution for future use would be to have a default json in the asset folder and have the server send it over, that way we could just simply swap the file and be done with it, or even have the server check for extra files that override the default without replacing it so it wouldn't cause any git conflicts...

I'll see if I can add that sometime soon, it ought to to come in handy.

gxomalis commented 4 months ago

Hey! Big thanks for your quick reply. Hm okay I'm a newbie in the Linux world and everything so please bare with me. Here's what I did now. I'm using the ros2 version of Vizanti.

  1. I found persistent.js in /vizanti/vizanti_server/public/js/modules

  2. I changed the constructor to this:

    constructor() {
    
        const defaultString = "{\"navbar\":[{\"type\":\"settings\",\"id\":\"settings_default\"},{\"type\":\"rosbridge\",\"id\":\"rosbridge_default\"},{\"type\":\"grid\",\"id\":\"grid_default\"},{\"type\":\"tf\",\"id\":\"tf_default\"},{\"type\":\"map\",\"id\":\"map_autoID_0\"},{\"type\":\"simplegoal\",\"id\":\"simplegoal_autoID_1\"},{\"type\":\"path\",\"id\":\"path_autoID_2\"},{\"type\":\"teleop\",\"id\":\"teleop_autoID_4\"},{\"type\":\"scan\",\"id\":\"scan_autoID_5\"},{\"type\":\"robotmodel\",\"id\":\"robotmodel_autoID_6\"}],\"view\":{\"center\":{\"x\":2.705681638721338,\"y\":0.5385767082942933},\"scale\":46.10223738657773},\"grid_default\":{\"size\":1,\"thickness\":1,\"colour\":\"#3e556a\"},\"tf_default\":{\"show_names\":true,\"show_axes\":true,\"show_lines\":true,\"scale\":1,\"frame_visibility\":{\"base_link\":true,\"odom\":true,\"lower\":true,\"LDS-01\":true,\"solid\":true,\"leftwheel\":true,\"rightwheel\":true,\"map\":true,\"base_footprint\":true,\"screw0\":true,\"screw1\":true,\"screw2\":true,\"screw3\":true,\"accelerometer\":true,\"compass\":true,\"gps\":true,\"gyro\":true,\"imu_link\":true,\"inertial_unit\":true,\"wheel_left_link\":true,\"wheel_right_link\":true,\"camera_link\":true,\"camera_rgb_frame\":true,\"camera_rgb_optical_frame\":true,\"caster_back_left_link\":true,\"caster_back_right_link\":true,\"base_scan\":true}},\"settings_default\":{\"fixed_frame\":\"map\",\"background_color\":\"#273444\"},\"uid\":15,\"map_autoID_0\":{\"topic\":\"/map\",\"opacity\":\"0.65\",\"costmap_mode\":false,\"throttle\":\"1000\",\"use_timestamp\":false},\"simplegoal_autoID_1\":{\"topic\":\"/goal_pose\"},\"path_autoID_2\":{\"topic\":\"/local_plan\",\"color\":\"#15ff00\"},\"posewithcovariancestamped_autoID_3\":{\"topic\":\"/amcl_pose\",\"scale\":1},\"teleop_autoID_4\":{\"topic\":\"/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"87.26287262872629%\",\"joy_offset_y\":\"84%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"path_autoID_5\":{\"topic\":\"/local_plan\",\"color\":\"#5ed753\"},\"waypoints_autoID_6\":{\"topic\":\"/plan\",\"fixed_frame\":\"\",\"points\":[],\"start_closest\":true},\"path_autoID_7\":{\"topic\":\"/plan\",\"color\":\"#5ed753\"},\"path_autoID_8\":{\"topic\":\"/plan\",\"color\":\"#5ed753\"},\"path_autoID_9\":{\"topic\":\"/plan_smoothed\",\"color\":\"#5ed753\"},\"path_autoID_10\":{\"topic\":\"/received_global_plan\",\"color\":\"#5ed753\"},\"path_autoID_11\":{\"topic\":\"/transformed_global_plan\",\"color\":\"#5ed753\"},\"path_autoID_12\":{\"topic\":\"/plan\",\"color\":\"#5ed753\"},\"waypoints_autoID_13\":{\"topic\":\"/plan\",\"fixed_frame\":\"map\",\"points\":[],\"start_closest\":true},\"map_autoID_14\":{\"topic\":\"/map\",\"opacity\":\"0.7\",\"costmap_mode\":false,\"throttle\":\"1000\",\"use_timestamp\":false},\"path_autoID_15\":{\"topic\":\"/local_plan\",\"color\":\"#5ed753\"},\"scan_autoID_5\":{\"topic\":\"/scan\",\"opacity\":\"0.5\",\"thickness\":\"0.07\",\"color\":\"#ff1100\",\"throttle\":\"2000\"},\"robotmodel_autoID_6\":{\"frame\":\"base_link\",\"sprite\":\"pancake\",\"length\":\"0.5\"},\"simplegoal_autoID_7\":{\"topic\":\"/goal_pose\"},\"button_autoID_8\":{\"topic\":\"\",\"text\":\"Text\",\"typedict\":{}},\"teleop_autoID_9\":{\"topic\":\"/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"50%\",\"joy_offset_y\":\"85%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"initialpose_autoID_10\":{\"topic\":\"/initialpose\"},\"robotmodel_autoID_7\":{\"frame\":\"base_link\",\"sprite\":\"4wd\",\"length\":\"0.5\"},\"robotmodel_autoID_8\":{\"frame\":\"base_link\",\"sprite\":\"4wd\",\"length\":\"0.5\"},\"tf_autoID_9\":{\"show_names\":true,\"show_axes\":true,\"show_lines\":true,\"scale\":1,\"frame_visibility\":{}},\"teleop_autoID_10\":{\"topic\":\"/turtle1/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"83.33333333333334%\",\"joy_offset_y\":\"85.16666666666667%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"teleop_autoID_11\":{\"topic\":\"/turtle1/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"50%\",\"joy_offset_y\":\"85%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"teleop_autoID_12\":{\"topic\":\"/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"50%\",\"joy_offset_y\":\"85%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"teleop_autoID_13\":{\"topic\":\"/turtle1/cmd_vel\",\"linear_velocity\":0.4,\"angular_velocity\":0.8,\"joy_offset_x\":\"50%\",\"joy_offset_y\":\"85%\",\"accel\":0.05,\"invert_angular\":true,\"holonomic_swap\":false},\"markerarray_autoID_14\":{\"topic\":\"/marker\"}}";
    
        if (localStorage.hasOwnProperty("settings")) {
            this.fromJSON(localStorage.getItem("settings"));
        }else{
            this.fromJSON(defaultString);
        }
    }
  3. I save the file, rebuild vizanti and then I run it.

  4. I cleared cookies and history on my browser, I visit localhost:5000 and it's the same as before.

Am I missing something or am I doing something wrong?

MoffKalast commented 4 months ago

Hmm that sounds like that should work. It might be that the cookie deletion didn't include local storage, since these aren't technically cookies. Does it do the same in an incognito window?

You can check what's being saved by opening F12 -> Storage -> Local Storage, then there might still be a key labelled "settings" and you can delete it manually.

gxomalis commented 4 months ago

Yes same in incognito and after deleting storage manually...

MoffKalast commented 4 months ago

I've given your exact code a test run on my end and I seem to get the defaults loaded right. Triggering "reset layout to default" in global settings also sets it to the new json as it should.

Maybe there's something with colcon not copying files over correctly, it shouldn't happen typically but it might be worth trying a fresh rebuild in case it's ignoring the file changes.

gxomalis commented 4 months ago

Okay it worked! I rebuilt it from scratch and it works very good. Big big thanks! One more thing and I will close the issue, how did you turn the layout.json file I included in the defaultString you provided?

MoffKalast commented 4 months ago

Ah glad it works :+1:

I just threw it into https://jsontostring.com, and let's actually leave it open for the time being. I could use a reminder to implement this properly with file swapping.

gxomalis commented 4 months ago

Perfect. Big thanks again for the help!

MoffKalast commented 2 months ago

Hey @gxomalis, I've finally gotten around to implementing this properly, you can check out the implementation in PR #75 for Humble or #76 for Noetic. The general idea is that you can specify a path in the launch file which points to an exported json and it'll use it as the default.

It seems to work reliably on my end, but it would be a great help if you could give it a quick test if you're still using this makeshift setup.

gxomalis commented 1 month ago

Hey @MoffKalast , I will look into it as soon as I can! Thanks for taking the time and making this happen! I'll let you know when I test it.