multimodalsim-viewer 0.1.2.0__py3-none-any.whl → 0.1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multimodalsim_viewer/common/utils.py +1 -10
- multimodalsim_viewer/server/data_manager.py +0 -28
- multimodalsim_viewer/server/http_routes.py +113 -89
- multimodalsim_viewer/server/scripts.py +1 -5
- multimodalsim_viewer/server/server.py +8 -12
- multimodalsim_viewer/server/simulation_manager.py +456 -244
- multimodalsim_viewer/ui/static/chunk-IILFIRWZ.js +7 -0
- multimodalsim_viewer/ui/static/{chunk-RHGMGEGM.js → chunk-L2ZL6Q4W.js} +1 -1
- multimodalsim_viewer/ui/static/index.html +2 -2
- multimodalsim_viewer/ui/static/{main-APJ3N7UX.js → main-XYH7FCFC.js} +172 -171
- multimodalsim_viewer/ui/static/styles-2KWGWV5P.css +1 -0
- {multimodalsim_viewer-0.1.2.0.dist-info → multimodalsim_viewer-0.1.4.0.dist-info}/METADATA +1 -1
- {multimodalsim_viewer-0.1.2.0.dist-info → multimodalsim_viewer-0.1.4.0.dist-info}/RECORD +16 -16
- multimodalsim_viewer/ui/static/chunk-BQ2VC5TN.js +0 -7
- multimodalsim_viewer/ui/static/styles-257KETL3.css +0 -1
- {multimodalsim_viewer-0.1.2.0.dist-info → multimodalsim_viewer-0.1.4.0.dist-info}/WHEEL +0 -0
- {multimodalsim_viewer-0.1.2.0.dist-info → multimodalsim_viewer-0.1.4.0.dist-info}/entry_points.txt +0 -0
- {multimodalsim_viewer-0.1.2.0.dist-info → multimodalsim_viewer-0.1.4.0.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,6 @@ from json import dumps
|
|
9
9
|
from dotenv import dotenv_values
|
10
10
|
from filelock import FileLock
|
11
11
|
from flask import request
|
12
|
-
from flask_socketio import emit
|
13
12
|
|
14
13
|
environment = {}
|
15
14
|
|
@@ -147,19 +146,11 @@ def build_simulation_id(name: str) -> tuple[str, str]:
|
|
147
146
|
return simulation_id, start_time
|
148
147
|
|
149
148
|
|
150
|
-
def log(message: str, auth_type: str, level=logging.INFO
|
149
|
+
def log(message: str, auth_type: str, level=logging.INFO) -> None:
|
151
150
|
if auth_type == "server":
|
152
151
|
logging.log(level, "[%s] %s", auth_type, message)
|
153
|
-
if should_emit:
|
154
|
-
emit("log", f"{level} [{auth_type}] {message}", to=CLIENT_ROOM)
|
155
152
|
else:
|
156
153
|
logging.log(level, "[%s] %s %s", auth_type, get_session_id(), message)
|
157
|
-
if should_emit:
|
158
|
-
emit(
|
159
|
-
"log",
|
160
|
-
f"{level} [{auth_type}] {get_session_id()} {message}",
|
161
|
-
to=CLIENT_ROOM,
|
162
|
-
)
|
163
154
|
|
164
155
|
|
165
156
|
def verify_simulation_name(name: str | None) -> str | None:
|
@@ -499,34 +499,6 @@ class SimulationVisualizationDataManager: # pylint: disable=too-many-public-met
|
|
499
499
|
|
500
500
|
return polylines, version
|
501
501
|
|
502
|
-
@staticmethod
|
503
|
-
def get_all_simulation_states(simulation_id: str) -> tuple[list[str], dict[list[str]]]:
|
504
|
-
states = []
|
505
|
-
updates = {}
|
506
|
-
|
507
|
-
sorted_states = SimulationVisualizationDataManager.get_sorted_states(simulation_id)
|
508
|
-
|
509
|
-
for update_index, timestamp in sorted_states:
|
510
|
-
file_path = SimulationVisualizationDataManager.get_saved_simulation_state_file_path(
|
511
|
-
simulation_id, update_index, timestamp
|
512
|
-
)
|
513
|
-
|
514
|
-
lock = FileLock(f"{file_path}.lock")
|
515
|
-
|
516
|
-
with lock:
|
517
|
-
with open(file_path, "r", encoding="utf-8") as file:
|
518
|
-
state_data = file.readline()
|
519
|
-
states.append(state_data)
|
520
|
-
|
521
|
-
updates_data = file.readlines()
|
522
|
-
current_state_updates = []
|
523
|
-
for update_data in updates_data:
|
524
|
-
current_state_updates.append(update_data)
|
525
|
-
|
526
|
-
updates[update_index] = current_state_updates
|
527
|
-
|
528
|
-
return states, updates
|
529
|
-
|
530
502
|
# MARK: +- Simulation Data
|
531
503
|
@staticmethod
|
532
504
|
def get_output_data_directory_path() -> str:
|
@@ -3,130 +3,154 @@ import os
|
|
3
3
|
import shutil
|
4
4
|
import tempfile
|
5
5
|
import zipfile
|
6
|
+
from typing import Callable
|
6
7
|
|
7
8
|
from flask import Blueprint, jsonify, request, send_file
|
8
9
|
|
9
10
|
from multimodalsim_viewer.server.data_manager import SimulationVisualizationDataManager
|
11
|
+
from multimodalsim_viewer.server.simulation_manager import SimulationManager
|
10
12
|
|
11
|
-
http_routes = Blueprint("http_routes", __name__)
|
12
13
|
|
13
|
-
|
14
|
+
class InvalidFilesRequestError(Exception):
|
15
|
+
def __init__(self, error_message: str) -> None:
|
16
|
+
self.error_message = error_message
|
17
|
+
|
18
|
+
|
19
|
+
def http_routes(simulation_manager: SimulationManager): # pylint: disable=too-many-statements
|
20
|
+
blueprint = Blueprint("http_routes", __name__)
|
14
21
|
|
22
|
+
# MARK: Helpers
|
23
|
+
def get_unique_folder_name(base_path, folder_name):
|
24
|
+
counter = 1
|
25
|
+
original_name = folder_name
|
26
|
+
while os.path.exists(os.path.join(base_path, folder_name)):
|
27
|
+
folder_name = f"{original_name}_({counter})"
|
28
|
+
counter += 1
|
29
|
+
return folder_name
|
30
|
+
|
31
|
+
def zip_folder(folder_path, zip_name):
|
32
|
+
if not os.path.isdir(folder_path):
|
33
|
+
return None
|
34
|
+
|
35
|
+
zip_path = os.path.join(tempfile.gettempdir(), f"{zip_name}.zip")
|
36
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
37
|
+
for root, _, files in os.walk(folder_path):
|
38
|
+
for file in files:
|
39
|
+
file_path = os.path.join(root, file)
|
40
|
+
zip_file.write(file_path, os.path.relpath(file_path, folder_path))
|
15
41
|
|
16
|
-
|
17
|
-
counter = 1
|
18
|
-
original_name = folder_name
|
19
|
-
while os.path.exists(os.path.join(base_path, folder_name)):
|
20
|
-
folder_name = f"{original_name}_({counter})"
|
21
|
-
counter += 1
|
22
|
-
return folder_name
|
42
|
+
return zip_path
|
23
43
|
|
44
|
+
def save_and_extract_zip(path: str, files):
|
45
|
+
try:
|
46
|
+
if "file" not in files:
|
47
|
+
raise InvalidFilesRequestError("No file part")
|
24
48
|
|
25
|
-
|
26
|
-
|
27
|
-
|
49
|
+
file = files["file"]
|
50
|
+
if file.filename == "":
|
51
|
+
raise InvalidFilesRequestError("No selected file")
|
28
52
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
for file in files:
|
33
|
-
file_path = os.path.join(root, file)
|
34
|
-
zip_file.write(file_path, os.path.relpath(file_path, folder_path))
|
53
|
+
# Create temporary zip file
|
54
|
+
zip_path = os.path.join(tempfile.gettempdir(), file.filename)
|
55
|
+
file.save(zip_path)
|
35
56
|
|
36
|
-
|
57
|
+
# Extract files
|
58
|
+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
59
|
+
zip_ref.extractall(path)
|
60
|
+
logging.info("Extracted files: %s", zip_ref.namelist())
|
37
61
|
|
62
|
+
# Let the exception propagate
|
63
|
+
finally:
|
64
|
+
# Remove temporary zip file
|
65
|
+
if os.path.exists(zip_path):
|
66
|
+
os.remove(zip_path)
|
38
67
|
|
39
|
-
def handle_zip_upload(folder_path):
|
40
|
-
|
41
|
-
|
68
|
+
def handle_zip_upload(folder_path, on_success: Callable | None = None):
|
69
|
+
parent_dir = os.path.dirname(folder_path)
|
70
|
+
base_folder_name = os.path.basename(folder_path)
|
42
71
|
|
43
|
-
|
44
|
-
|
72
|
+
unique_folder_name = get_unique_folder_name(parent_dir, base_folder_name)
|
73
|
+
actual_folder_path = os.path.join(parent_dir, unique_folder_name)
|
45
74
|
|
46
|
-
|
75
|
+
os.makedirs(actual_folder_path, exist_ok=True)
|
47
76
|
|
48
|
-
|
49
|
-
|
77
|
+
try:
|
78
|
+
save_and_extract_zip(actual_folder_path, request.files)
|
79
|
+
except InvalidFilesRequestError as error:
|
80
|
+
return jsonify({"error": error.error_message}), 400
|
81
|
+
except zipfile.BadZipFile:
|
82
|
+
return jsonify({"error": "Invalid ZIP file"}), 400
|
50
83
|
|
51
|
-
|
52
|
-
|
53
|
-
|
84
|
+
response_message = f"Folder '{unique_folder_name}' uploaded successfully"
|
85
|
+
if unique_folder_name != base_folder_name:
|
86
|
+
response_message += f" (renamed from '{base_folder_name}')"
|
54
87
|
|
55
|
-
|
56
|
-
|
88
|
+
if on_success:
|
89
|
+
on_success()
|
57
90
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
91
|
+
return (
|
92
|
+
jsonify({"message": response_message, "folderName": unique_folder_name}),
|
93
|
+
201,
|
94
|
+
)
|
62
95
|
|
63
|
-
|
64
|
-
|
65
|
-
|
96
|
+
# MARK: Instances
|
97
|
+
@blueprint.route("/api/input_data/<folder_name>", methods=["GET"])
|
98
|
+
def export_input_data(folder_name):
|
99
|
+
folder_path = SimulationVisualizationDataManager.get_input_data_directory_path(folder_name)
|
100
|
+
logging.info("Requested folder: %s", folder_path)
|
66
101
|
|
67
|
-
|
68
|
-
|
69
|
-
|
102
|
+
zip_path = zip_folder(folder_path, folder_name)
|
103
|
+
if not zip_path:
|
104
|
+
return jsonify({"error": "Folder not found"}), 404
|
70
105
|
|
71
|
-
|
72
|
-
jsonify({"message": response_message, "actual_folder_name": unique_folder_name}),
|
73
|
-
201,
|
74
|
-
)
|
106
|
+
return send_file(zip_path, as_attachment=True)
|
75
107
|
|
108
|
+
@blueprint.route("/api/input_data/<folder_name>", methods=["POST"])
|
109
|
+
def import_input_data(folder_name):
|
110
|
+
folder_path = SimulationVisualizationDataManager.get_input_data_directory_path(folder_name)
|
111
|
+
return handle_zip_upload(folder_path)
|
76
112
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
113
|
+
@blueprint.route("/api/input_data/<folder_name>", methods=["DELETE"])
|
114
|
+
def delete_input_data(folder_name):
|
115
|
+
folder_path = SimulationVisualizationDataManager.get_input_data_directory_path(folder_name)
|
116
|
+
if not os.path.isdir(folder_path):
|
117
|
+
return jsonify({"error": "Folder not found"}), 404
|
82
118
|
|
83
|
-
|
84
|
-
|
85
|
-
return jsonify({"error": "Folder not found"}), 404
|
119
|
+
shutil.rmtree(folder_path)
|
120
|
+
return jsonify({"message": f"Folder '{folder_name}' deleted successfully"})
|
86
121
|
|
87
|
-
|
122
|
+
# MARK: Visualizations
|
123
|
+
@blueprint.route("/api/simulation/<folder_name>", methods=["GET"])
|
124
|
+
def export_saved_simulation(folder_name):
|
125
|
+
folder_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(folder_name)
|
126
|
+
logging.info("Requested folder: %s", folder_path)
|
88
127
|
|
128
|
+
zip_path = zip_folder(folder_path, folder_name)
|
129
|
+
if not zip_path:
|
130
|
+
return jsonify({"error": "Folder not found"}), 404
|
89
131
|
|
90
|
-
|
91
|
-
def import_input_data(folder_name):
|
92
|
-
folder_path = SimulationVisualizationDataManager.get_input_data_directory_path(folder_name)
|
93
|
-
return handle_zip_upload(folder_path)
|
132
|
+
return send_file(zip_path, as_attachment=True)
|
94
133
|
|
134
|
+
@blueprint.route("/api/simulation/<folder_name>", methods=["POST"])
|
135
|
+
def import_saved_simulation(folder_name):
|
136
|
+
folder_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(folder_name)
|
95
137
|
|
96
|
-
|
97
|
-
|
98
|
-
folder_path = SimulationVisualizationDataManager.get_input_data_directory_path(folder_name)
|
99
|
-
if not os.path.isdir(folder_path):
|
100
|
-
return jsonify({"error": "Folder not found"}), 404
|
138
|
+
def on_success():
|
139
|
+
simulation_manager.emit_simulation(folder_name)
|
101
140
|
|
102
|
-
|
103
|
-
return jsonify({"message": f"Folder '{folder_name}' deleted successfully"})
|
141
|
+
return handle_zip_upload(folder_path, on_success)
|
104
142
|
|
143
|
+
@blueprint.route("/api/simulation/<folder_name>", methods=["DELETE"])
|
144
|
+
def delete_saved_simulation(folder_name):
|
145
|
+
folder_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(folder_name)
|
105
146
|
|
106
|
-
|
107
|
-
|
108
|
-
def export_saved_simulation(folder_name):
|
109
|
-
folder_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(folder_name)
|
110
|
-
logging.info("Requested folder: %s", folder_path)
|
147
|
+
if not os.path.isdir(folder_path):
|
148
|
+
return jsonify({"error": "Folder not found"}), 404
|
111
149
|
|
112
|
-
|
113
|
-
if not zip_path:
|
114
|
-
return jsonify({"error": "Folder not found"}), 404
|
150
|
+
shutil.rmtree(folder_path)
|
115
151
|
|
116
|
-
|
152
|
+
simulation_manager.on_simulation_delete(folder_name)
|
117
153
|
|
154
|
+
return jsonify({"message": f"Folder '{folder_name}' deleted successfully"})
|
118
155
|
|
119
|
-
|
120
|
-
def import_saved_simulation(folder_name):
|
121
|
-
folder_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(folder_name)
|
122
|
-
return handle_zip_upload(folder_path)
|
123
|
-
|
124
|
-
|
125
|
-
@http_routes.route("/api/simulation/<folder_name>", methods=["DELETE"])
|
126
|
-
def delete_saved_simulation(folder_name):
|
127
|
-
folder_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(folder_name)
|
128
|
-
if not os.path.isdir(folder_path):
|
129
|
-
return jsonify({"error": "Folder not found"}), 404
|
130
|
-
|
131
|
-
shutil.rmtree(folder_path)
|
132
|
-
return jsonify({"message": f"Folder '{folder_name}' deleted successfully"})
|
156
|
+
return blueprint
|
@@ -7,11 +7,7 @@ from requests.exceptions import ConnectionError as RequestsConnectionError
|
|
7
7
|
from socketio import Client
|
8
8
|
from socketio.exceptions import ConnectionError as SocketIOConnectionError
|
9
9
|
|
10
|
-
from multimodalsim_viewer.common.utils import
|
11
|
-
CLIENT_PORT,
|
12
|
-
HOST,
|
13
|
-
SERVER_PORT,
|
14
|
-
)
|
10
|
+
from multimodalsim_viewer.common.utils import CLIENT_PORT, HOST, SERVER_PORT
|
15
11
|
from multimodalsim_viewer.server.server import configure_server
|
16
12
|
from multimodalsim_viewer.server.simulation import (
|
17
13
|
configure_simulation_parser,
|
@@ -14,17 +14,17 @@ from multimodalsim_viewer.server.simulation_manager import SimulationManager
|
|
14
14
|
def configure_server() -> tuple[Flask, SocketIO]: # pylint: disable=too-many-statements, too-many-locals
|
15
15
|
app = Flask(__name__)
|
16
16
|
|
17
|
+
socketio = SocketIO(app, cors_allowed_origins="*")
|
18
|
+
|
19
|
+
simulation_manager = SimulationManager(socketio)
|
20
|
+
|
17
21
|
# Register HTTP routes
|
18
22
|
CORS(app)
|
19
|
-
app.register_blueprint(http_routes)
|
20
|
-
|
21
|
-
socketio = SocketIO(app, cors_allowed_origins="*")
|
23
|
+
app.register_blueprint(http_routes(simulation_manager))
|
22
24
|
|
23
25
|
# key = session id, value = auth type
|
24
26
|
sockets_types_by_session_id = {}
|
25
27
|
|
26
|
-
simulation_manager = SimulationManager()
|
27
|
-
|
28
28
|
# MARK: Main events
|
29
29
|
@socketio.on("connect")
|
30
30
|
def on_connect(auth):
|
@@ -69,9 +69,9 @@ def configure_server() -> tuple[Flask, SocketIO]: # pylint: disable=too-many-st
|
|
69
69
|
simulation_manager.resume_simulation(simulation_id)
|
70
70
|
|
71
71
|
@socketio.on("get-simulations")
|
72
|
-
def on_client_get_simulations():
|
73
|
-
log("getting simulations", "client")
|
74
|
-
simulation_manager.emit_simulations()
|
72
|
+
def on_client_get_simulations(loaded_simulations_ids: list[str]):
|
73
|
+
log(f"getting simulations with already loaded ids {loaded_simulations_ids}", "client")
|
74
|
+
simulation_manager.emit_simulations(loaded_simulations_ids)
|
75
75
|
|
76
76
|
@socketio.on("get-available-data")
|
77
77
|
def on_client_get_data():
|
@@ -136,10 +136,6 @@ def configure_server() -> tuple[Flask, SocketIO]: # pylint: disable=too-many-st
|
|
136
136
|
log(f"simulation {simulation_id} resumed", "simulation")
|
137
137
|
simulation_manager.on_simulation_resume(simulation_id)
|
138
138
|
|
139
|
-
@socketio.on("log")
|
140
|
-
def on_simulation_log(simulation_id, message):
|
141
|
-
log(f"simulation {simulation_id}: {message}", "simulation", logging.DEBUG)
|
142
|
-
|
143
139
|
@socketio.on("simulation-update-time")
|
144
140
|
def on_simulation_update_time(simulation_id, timestamp):
|
145
141
|
log(
|