Commit aa02e01a authored by Karol Actun's avatar Karol Actun
Browse files

- added video recording

- scenario and screenshots "save as" buttons
- anti aliasing (default 4 samples)
- bug fixes + many small changes to the default gui
- svg screenshots (toms_svg_generator, only for TriagularGrid)
- todo next: svg screenshots for all grids
parent 14b47aa3
......@@ -162,4 +162,6 @@ config.ini
.dropbox
outputs/
lib/__pycache__/*
\ No newline at end of file
screenshots/
videos/
lib/__pycache__/*
......@@ -19,6 +19,8 @@ For Linux:
6. sudo pip3 install PyQt5
7. sudo pip3 install opencv-python
for older Systems (e.g. Ubuntu 14.04) install the PyQt5 version 5.10.1
6. sudo pip3 install PyQt5==5.10.1
......@@ -52,6 +54,7 @@ For Windows/Linux/MacOs:
4. PyOpenGL
5. Pillow
6. PyQt5 (in version 5.10.1 for older Systems like Ubuntu 14.04)
7. opencv-python
- press Okey
- wait until everything is installed
- chose Run->swarm-sim.py
......
from PyQt5.QtGui import QColor, QIntValidator
from PyQt5.QtWidgets import (QVBoxLayout, QPushButton, QColorDialog, QRadioButton, QLabel, QTabWidget,
QSlider, QHBoxLayout, QCheckBox, QTabBar, QLineEdit)
QSlider, QHBoxLayout, QCheckBox, QTabBar, QLineEdit, QGroupBox, QComboBox, QStyle)
from PyQt5.QtCore import Qt
from lib.swarm_sim_header import eprint
from lib.visualization.utils import show_msg
start_stop_button = None
world = None
vis = None
simtab = None
def create_gui(w, v):
global world, vis
global world, vis, simtab
world = w
vis = v
tabbar = QTabWidget()
tabbar.setMinimumWidth(200)
tabbar.addTab(sim_tab(), "Simulation")
simtab = sim_tab()
tabbar.addTab(simtab, "Simulation")
tabbar.addTab(vis_tab(), "Visualization")
tabbar.addTab(grid_tab(), "Grid")
tabbar.addTab(matter_tab(), "Matter")
tabbar.addTab(help_tab(), "Help")
return tabbar
def set_disable_sim(disable_flag):
global simtab
simtab.setDisabled(disable_flag)
def key_handler(key, w, v):
if key == Qt.Key_Space:
start_stop_button.click()
......@@ -77,61 +83,153 @@ def create_slider(tick_interval: int, tick_position: int, max_position: int, min
def sim_tab():
tab = QTabBar()
layout = QVBoxLayout()
global start_stop_button
# start stop button
start_stop_button = QPushButton("start Simulation")
start_stop_button.setIcon(start_stop_button.style().standardIcon(QStyle.SP_MediaPlay))
start_stop_button.setStyleSheet("font-weight:bold;")
status = QLabel("Simulation not running")
status.setStyleSheet("color:#ff0000;")
status.setStyleSheet("font-weight:bold;color:#bb4444;")
def start_stop_sim():
vis.start_stop()
if vis.is_running():
status.setText("Simulation running")
status.setStyleSheet("color:#00ff00;")
status.setStyleSheet("font-weight:bold;color:#44bb44;")
start_stop_button.setText("pause Simulation")
start_stop_button.setIcon(start_stop_button.style().standardIcon(QStyle.SP_MediaPause))
else:
status.setText("Simulation paused")
status.setStyleSheet("color:#0000ff;")
status.setStyleSheet("font-weight:bold;color:#4444bb;")
start_stop_button.setText("unpause Simulation")
start_stop_button.setIcon(start_stop_button.style().standardIcon(QStyle.SP_MediaPlay))
start_stop_button.clicked.connect(start_stop_sim)
# screenshots button
screenshot_button = QPushButton("take Screenshot")
# screenshot buttons
quick_screenshot = QPushButton("quick")
save_as_screenshot = QPushButton("save as")
def qs():
vis.take_screenshot(True)
def take_screenshot():
vis.take_screenshot()
screenshot_button.clicked.connect(take_screenshot)
def sas():
vis.take_screenshot(False)
quick_screenshot.clicked.connect(qs)
save_as_screenshot.clicked.connect(sas)
# WARNING: VECTOR SCREENSHOTS ONLY FOR TRIANGULARGRID IMPLEMENTED!
vector_screenshot = QPushButton("export as vector graphics (svg)")
def eas():
vis.take_vector_screenshot()
vector_screenshot.clicked.connect(eas)
screenshot_buttons = QHBoxLayout()
screenshot_buttons.addWidget(quick_screenshot)
screenshot_buttons.addWidget(save_as_screenshot)
vbox = QVBoxLayout()
vbox.addLayout(screenshot_buttons)
vbox.addWidget(vector_screenshot)
screenshot_box = QGroupBox("screenshot")
screenshot_box.setLayout(vbox)
# recording buttons
start_recording_button = QPushButton("start")
del_recording_button = QPushButton("delete")
export_recording_button = QPushButton("export")
hbox = QHBoxLayout()
hbox.addWidget(start_recording_button)
hbox.addWidget(del_recording_button)
hbox.addWidget(export_recording_button)
recording_box = QGroupBox("Recording")
recording_box.setLayout(hbox)
def startstoprec():
if vis.is_recording():
start_recording_button.setText("start")
recording_box.setTitle("recording")
if vis.is_running():
start_stop_sim()
vis.stop_recording()
else:
recording_box.setTitle("Recording (running)")
start_recording_button.setText("stop")
vis.start_recording()
start_recording_button.clicked.connect(startstoprec)
def del_rec():
if vis.is_recording():
startstoprec()
vis.delete_recording()
del_recording_button.clicked.connect(del_rec)
export_recording_button.clicked.connect(vis.export_recording)
# reset button
reset_button = QPushButton("reset Simulation")
def reset_sim():
world.reset()
start_stop_button.setText("start Simulation")
start_stop_button.setIcon(start_stop_button.style().standardIcon(QStyle.SP_MediaPlay))
status.setText("Simulation not running")
status.setStyleSheet("color:#ff0000;")
status.setStyleSheet("font-weight:bold;color:#bb4444;")
reset_button.clicked.connect(reset_sim)
save_scenario_button = QPushButton("save Scenario")
quick_scenario_button = QPushButton("quick")
save_as_scenario_button = QPushButton("save as")
def qs_scenario(): world.save_scenario(True)
def sas_scenario(): world.save_scenario(False)
def save_scenario():
world.save_scenario()
quick_scenario_button.clicked.connect(qs_scenario)
save_as_scenario_button.clicked.connect(sas_scenario)
scenario_buttons = QHBoxLayout()
scenario_buttons.addWidget(quick_scenario_button)
scenario_buttons.addWidget(save_as_scenario_button)
scenario_box = QGroupBox("scenario")
scenario_box.setLayout(scenario_buttons)
sim_box = QGroupBox("main controls")
vbox = QVBoxLayout()
vbox.addWidget(status)
vbox.addWidget(start_stop_button)
vbox.addLayout(get_rps_slider())
vbox.addWidget(reset_button)
sim_box.setLayout(vbox)
layout.addWidget(sim_box)
layout.addWidget(screenshot_box)
layout.addWidget(scenario_box)
layout.addWidget(recording_box)
new_matter_box = QGroupBox("new matter")
vbox = QVBoxLayout()
vbox.addLayout(get_matter_radios())
vbox.addWidget(get_new_matter_color_picker())
new_matter_box.setLayout(vbox)
layout.addWidget(new_matter_box)
save_scenario_button.clicked.connect(save_scenario)
layout.addWidget(status, alignment=Qt.AlignBaseline)
layout.addWidget(start_stop_button, alignment=Qt.AlignBaseline)
layout.addLayout(get_rps_slider())
layout.addWidget(screenshot_button, alignment=Qt.AlignBaseline)
layout.addWidget(reset_button, alignment=Qt.AlignBaseline)
layout.addWidget(save_scenario_button, alignment=Qt.AlignBaseline)
layout.addLayout(get_matter_radios())
layout.addStretch(0)
tab.setLayout(layout)
return tab
def get_new_matter_color_picker():
cp_button = QPushButton("change color of new matter")
def cp():
cd = QColorDialog()
cd.setCurrentColor(QColor.fromRgbF(*vis.get_added_matter_color()))
cd.exec()
if cd.result() == 1:
vis.set_added_matter_color((cd.selectedColor().getRgbF()))
cp_button.clicked.connect(cp)
return cp_button
def get_matter_radios():
p_radio = QRadioButton("particle")
p_radio.setChecked(False)
......@@ -139,7 +237,6 @@ def get_matter_radios():
t_radio.setChecked(True)
l_radio = QRadioButton("location")
l_radio.setChecked(False)
desc = QLabel("matter type to be created/removed:")
def on_toggle():
if p_radio.isChecked():
......@@ -156,10 +253,9 @@ def get_matter_radios():
l_radio.toggled.connect(on_toggle)
vbox = QVBoxLayout()
vbox.addWidget(desc, alignment=Qt.AlignBaseline)
vbox.addWidget(p_radio, alignment=Qt.AlignBaseline)
vbox.addWidget(t_radio, alignment=Qt.AlignBaseline)
vbox.addWidget(l_radio, alignment=Qt.AlignBaseline)
vbox.addWidget(p_radio)
vbox.addWidget(t_radio)
vbox.addWidget(l_radio)
return vbox
......@@ -167,29 +263,43 @@ def get_matter_radios():
def get_rps_slider():
hbox = QVBoxLayout()
desc = QLabel("rounds per second (%d) : " % vis.get_rounds_per_second())
hbox.addWidget(desc, alignment=Qt.AlignBaseline)
hbox.addWidget(desc)
def set_rps(value):
vis.set_rounds_per_second(value)
desc.setText("rounds per second (%d) : " % vis.get_rounds_per_second())
hbox.addWidget(create_slider(10, 2, 120, 1, vis.get_rounds_per_second(), set_rps), alignment=Qt.AlignBaseline)
hbox.addWidget(create_slider(10, 2, 120, 1, vis.get_rounds_per_second(), set_rps))
return hbox
def vis_tab():
tab = QTabBar()
layout = QVBoxLayout()
layout.addLayout(get_projection_switch())
layout.addLayout(get_fov_slider())
layout.addLayout(get_render_distance_slider())
layout.addLayout(get_drag_sens_slider())
layout.addLayout(get_zoom_sens_slider())
layout.addLayout(get_rota_sens_slider())
layout.addLayout(get_vis_show_checkboxes())
reset_position_button = QPushButton("reset position")
proj_box = QGroupBox("projection settings")
vbox = QVBoxLayout()
vbox.addLayout(get_projection_switch())
vbox.addLayout(get_fov_slider())
vbox.addLayout(get_render_distance_slider())
proj_box.setLayout(vbox)
layout.addWidget(proj_box)
sens_slider_box = QGroupBox("control sensitivity")
vbox = QVBoxLayout()
vbox.addLayout(get_drag_sens_slider())
vbox.addLayout(get_zoom_sens_slider())
vbox.addLayout(get_rota_sens_slider())
sens_slider_box.setLayout(vbox)
layout.addWidget(sens_slider_box)
misc_box = QGroupBox("miscellaneous")
vbox = QVBoxLayout()
vbox.addLayout(get_antialiasing_combobox())
vbox.addLayout(get_vis_show_checkboxes())
reset_position_button = QPushButton("reset camera position")
reset_position_button.clicked.connect(vis.reset_camera_position)
layout.addWidget(reset_position_button, alignment=Qt.AlignBaseline)
vbox.addWidget(reset_position_button)
misc_box.setLayout(vbox)
layout.addWidget(misc_box)
layout.addStretch(0)
tab.setLayout(layout)
return tab
......@@ -221,6 +331,8 @@ def matter_tab():
def get_scaler(mattertype):
if mattertype == "particle":
current_scaling = vis.get_particle_scaling()
def x_scaler_change(value):
cs = vis.get_particle_scaling()
new_scaling = (value/10.0, cs[1], cs[2])
......@@ -236,6 +348,8 @@ def get_scaler(mattertype):
new_scaling = (cs[0], cs[1], value/10.0)
vis.set_particle_scaling(new_scaling)
elif mattertype == "tile":
current_scaling = vis.get_tile_scaling()
def x_scaler_change(value):
cs = vis.get_tile_scaling()
new_scaling = (value / 10.0, cs[1], cs[2])
......@@ -251,6 +365,8 @@ def get_scaler(mattertype):
new_scaling = (cs[0], cs[1], value / 10.0)
vis.set_tile_scaling(new_scaling)
else:
current_scaling = vis.get_location_scaling()
def x_scaler_change(value):
cs = vis.get_location_scaling()
new_scaling = (value / 10.0, cs[1], cs[2])
......@@ -266,7 +382,6 @@ def get_scaler(mattertype):
new_scaling = (cs[0], cs[1], value / 10.0)
vis.set_location_scaling(new_scaling)
current_scaling = vis.get_particle_scaling()
x_desc = QLabel("x scale:")
y_desc = QLabel("y scale:")
z_desc = QLabel("z scale:")
......@@ -275,19 +390,19 @@ def get_scaler(mattertype):
z_scaler = create_slider(2, 2, 20, 1, current_scaling[0]*10, z_scaler_change)
hbox1 = QHBoxLayout()
hbox1.addWidget(x_desc, alignment=Qt.AlignBaseline)
hbox1.addWidget(x_scaler, alignment=Qt.AlignBaseline)
hbox1.addWidget(x_desc)
hbox1.addWidget(x_scaler)
hbox2 = QHBoxLayout()
hbox2.addWidget(y_desc, alignment=Qt.AlignBaseline)
hbox2.addWidget(y_scaler, alignment=Qt.AlignBaseline)
hbox2.addWidget(y_desc)
hbox2.addWidget(y_scaler)
hbox3 = QHBoxLayout()
hbox3.addWidget(z_desc, alignment=Qt.AlignBaseline)
hbox3.addWidget(z_scaler, alignment=Qt.AlignBaseline)
hbox3.addWidget(z_desc)
hbox3.addWidget(z_scaler)
vbox = QVBoxLayout()
vbox.addWidget(QLabel("%s scaling:" % mattertype), alignment=Qt.AlignBaseline)
vbox.addWidget(QLabel("%s scaling:" % mattertype))
vbox.addLayout(hbox1)
vbox.addLayout(hbox2)
vbox.addLayout(hbox3)
......@@ -298,20 +413,20 @@ def get_scaler(mattertype):
def get_fov_slider():
hbox = QVBoxLayout()
desc = QLabel("(only for perspective projection)\nfield of view (%d°) : " % vis.get_field_of_view())
hbox.addWidget(desc, alignment=Qt.AlignBaseline)
hbox.addWidget(desc)
def set_fov(value):
vis.set_field_of_view(value)
desc.setText("(only for perspective projection)\nfield of view (%d°) : " % vis.get_field_of_view())
hbox.addWidget(create_slider(10, 2, 120, 10, vis.get_field_of_view(), set_fov), alignment=Qt.AlignBaseline)
hbox.addWidget(create_slider(10, 2, 120, 10, vis.get_field_of_view(), set_fov))
return hbox
def get_drag_sens_slider():
hbox = QVBoxLayout()
desc = QLabel("drag sensitivity:")
hbox.addWidget(desc, alignment=Qt.AlignBaseline)
hbox = QHBoxLayout()
desc = QLabel("drag:")
hbox.addWidget(desc)
def set_ds(value):
vis.set_drag_sensitivity(5100-value)
......@@ -322,56 +437,57 @@ def get_drag_sens_slider():
def get_zoom_sens_slider():
hbox = QVBoxLayout()
desc = QLabel("zoom sensitivity:")
hbox.addWidget(desc, alignment=Qt.AlignBaseline)
hbox = QHBoxLayout()
desc = QLabel("zoom:")
hbox.addWidget(desc)
def set_zs(value):
vis.set_zoom_sensitivity(1001-value)
hbox.addWidget(create_slider(100, 2, 1000, 1, 1001-vis.get_zoom_sensitivity(), set_zs), alignment=Qt.AlignBaseline)
hbox.addWidget(create_slider(100, 2, 1000, 1, 1001-vis.get_zoom_sensitivity(), set_zs))
return hbox
def get_rota_sens_slider():
hbox = QVBoxLayout()
desc = QLabel("(only for 3D)\nrotation sensitivity:")
hbox.addWidget(desc, alignment=Qt.AlignBaseline)
hbox = QHBoxLayout()
desc = QLabel("(only 3D)\nrotation:")
hbox.addWidget(desc)
def set_rs(value):
vis.set_rotation_sensitivity(11-value)
hbox.addWidget(create_slider(1, 2, 10, 1, 11-vis.get_rotation_sensitivity(), set_rs), alignment=Qt.AlignBaseline)
hbox.addWidget(create_slider(1, 2, 10, 1, 11-vis.get_rotation_sensitivity(), set_rs))
return hbox
def get_projection_switch():
vbox = QVBoxLayout()
desc = QLabel("projection type:")
vbox.addWidget(desc, alignment=Qt.AlignBaseline)
vbox.addWidget(desc)
o = QRadioButton("orthographic")
def orth_toggle():
if o.isChecked():
vis.set_projection_type("ortho")
o.toggled.connect(orth_toggle)
p = QRadioButton("perspective")
def pers_toggle():
if p.isChecked():
vis.set_projection_type("perspective")
if vis.get_projection_type() == "ortho":
o.setChecked(True)
else:
p.setChecked(True)
def pers_toggle():
if p.isChecked():
vis.set_projection_type("perspective")
o.toggled.connect(orth_toggle)
p.toggled.connect(pers_toggle)
hbox = QHBoxLayout()
hbox.addWidget(o, alignment=Qt.AlignBaseline)
hbox.addWidget(p, alignment=Qt.AlignBaseline)
hbox.addWidget(o)
hbox.addWidget(p)
vbox.addLayout(hbox)
return vbox
......@@ -427,13 +543,13 @@ def get_color_picker():
vbox = QVBoxLayout()
desc = QLabel("change color of:")
vbox.addWidget(desc, alignment=Qt.AlignBaseline)
vbox.addWidget(desc)
hbox1 = QHBoxLayout()
hbox1.addWidget(lines_button, alignment=Qt.AlignBaseline)
hbox1.addWidget(locs_button, alignment=Qt.AlignBaseline)
hbox1.addWidget(lines_button)
hbox1.addWidget(locs_button)
hbox2 = QHBoxLayout()
hbox2.addWidget(bg_button, alignment=Qt.AlignBaseline)
hbox2.addWidget(brd_button, alignment=Qt.AlignBaseline)
hbox2.addWidget(bg_button)
hbox2.addWidget(brd_button)
vbox.addLayout(hbox1)
vbox.addLayout(hbox2)
return vbox
......@@ -442,13 +558,13 @@ def get_color_picker():
def get_render_distance_slider():
vbox = QVBoxLayout()
desc = QLabel("render distance (%d):" % vis.get_render_distance())
vbox.addWidget(desc, alignment=Qt.AlignBaseline)
vbox.addWidget(desc)
def set_mrd(value):
vis.set_render_distance(value)
desc.setText("render distance (%d):" % vis.get_render_distance())
vbox.addWidget(create_slider(10, 2, 5000, 5, vis.get_render_distance(), set_mrd), alignment=Qt.AlignBaseline)
vbox.addWidget(create_slider(500, 2, 5000, 5, vis.get_render_distance(), set_mrd))
return vbox
......@@ -480,11 +596,11 @@ def get_vis_show_checkboxes():
rl_cb.clicked.connect(rl_clicked)
hbox = QHBoxLayout()
hbox.addWidget(center_cb, alignment=Qt.AlignBaseline)
hbox.addWidget(focus_cb, alignment=Qt.AlignBaseline)
hbox.addWidget(rl_cb, alignment=Qt.AlignBaseline)
return hbox
vbox = QVBoxLayout()
vbox.addWidget(center_cb)
vbox.addWidget(focus_cb)
vbox.addWidget(rl_cb)
return vbox
def get_show_checkboxes():
......@@ -507,8 +623,8 @@ def get_show_checkboxes():
coords_cb.clicked.connect(coords_clicked)
hbox = QHBoxLayout()
hbox.addWidget(lines_cb, alignment=Qt.AlignBaseline)
hbox.addWidget(coords_cb, alignment=Qt.AlignBaseline)
hbox.addWidget(lines_cb)
hbox.addWidget(coords_cb)
if world.config_data.border:
border_cb = QCheckBox()
border_cb.setText("show border")
......@@ -518,7 +634,7 @@ def get_show_checkboxes():
vis.set_show_border(border_cb.isChecked())
border_cb.clicked.connect(border_clicked)
hbox.addWidget(border_cb, alignment=Qt.AlignBaseline)
hbox.addWidget(border_cb)
return hbox
......@@ -526,7 +642,7 @@ def get_show_checkboxes():
def get_grid_lines_scale_slider():
vbox = QVBoxLayout()
desc = QLabel("grid lines scale (%d%%):" % int(vis.get_grid_line_scaling()[0]*100))
vbox.addWidget(desc, alignment=Qt.AlignBaseline)
vbox.addWidget(desc)
def set_scale(value):
vis.set_grid_line_scaling([value/50.0, value/50.0, value/50.0])
......@@ -540,7 +656,7 @@ def get_grid_lines_scale_slider():
def get_grid_coordinates_scale_slider():
vbox = QVBoxLayout()
desc = QLabel("grid coordinates model scale (%d%%):" % int(vis.get_grid_coordinates_scaling()[0]*500))
vbox.addWidget(desc, alignment=Qt.AlignBaseline)
vbox.addWidget(desc)
def set_scale(value):
vis.set_grid_coordinates_scaling([value/1000.0, value/1000.0, value/1000.0])
......@@ -563,11 +679,30 @@ def recalculate_grid():
if size_edit.text().isnumeric():
vis.recalculate_grid(int(size_edit.text()))
else:
eprint("warning: grid size has to be a number")
show_msg("Grid size has to be a number.", 2)
rec_button.clicked.connect(on_click)
hbox.addWidget(rec_button, alignment=Qt.AlignBaseline)
hbox.addWidget(size_edit, alignment=Qt.AlignBaseline)
hbox.addWidget(rec_button)
hbox.addWidget(size_edit)
return hbox
def get_antialiasing_combobox():
hbox = QHBoxLayout()
combo = QComboBox()
combo.addItem("disabled", 0)
combo.addItem("2 samples", 2)
combo.addItem("4 samples", 4)
combo.addItem("8 samples", 8)
combo.setCurrentIndex(2)
def onchange():
vis.set_antialiasing(combo.currentData())
combo.currentIndexChanged.connect(onchange)
hbox.addWidget(QLabel("antialiasing:"))
hbox.addWidget(combo)
return hbox
\ No newline at end of file
......@@ -9,6 +9,7 @@ class ConfigData:
def __init__(self):
config = configparser.ConfigParser(allow_no_value=True)
config.read("config.ini")