ast_monitor 0.5.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.
Files changed (58) hide show
  1. ast_monitor/__init__.py +30 -0
  2. ast_monitor/basic_data.py +116 -0
  3. ast_monitor/digital_twin.py +155 -0
  4. ast_monitor/goals_processor.py +106 -0
  5. ast_monitor/gps_sensor.py +81 -0
  6. ast_monitor/hr_sensor.py +71 -0
  7. ast_monitor/html_request_handler.py +24 -0
  8. ast_monitor/icons/arrow_down.svg +61 -0
  9. ast_monitor/icons/arrow_left.svg +64 -0
  10. ast_monitor/icons/arrow_right.svg +64 -0
  11. ast_monitor/icons/arrow_up.svg +61 -0
  12. ast_monitor/icons/ascent.svg +64 -0
  13. ast_monitor/icons/bicycle.svg +65 -0
  14. ast_monitor/icons/clock.svg +148 -0
  15. ast_monitor/icons/distance.svg +136 -0
  16. ast_monitor/icons/heart.svg +79 -0
  17. ast_monitor/icons/licence.txt +1 -0
  18. ast_monitor/icons/play.svg +74 -0
  19. ast_monitor/icons/shutdown.svg +74 -0
  20. ast_monitor/icons/speed.svg +127 -0
  21. ast_monitor/icons/stop.svg +63 -0
  22. ast_monitor/interval_training.py +184 -0
  23. ast_monitor/mainwindow.py +1308 -0
  24. ast_monitor/map/README.md +59 -0
  25. ast_monitor/map/env.d.ts +1 -0
  26. ast_monitor/map/index.html +16 -0
  27. ast_monitor/map/package-lock.json +5243 -0
  28. ast_monitor/map/package.json +37 -0
  29. ast_monitor/map/public/favicon.ico +0 -0
  30. ast_monitor/map/src/App.vue +76 -0
  31. ast_monitor/map/src/assets/base.css +73 -0
  32. ast_monitor/map/src/assets/logo.svg +1 -0
  33. ast_monitor/map/src/assets/main.css +1 -0
  34. ast_monitor/map/src/components/DisplayCard.vue +68 -0
  35. ast_monitor/map/src/components/Map.vue +78 -0
  36. ast_monitor/map/src/components/RouteProgress.vue +102 -0
  37. ast_monitor/map/src/components/TrainingMetrics.vue +45 -0
  38. ast_monitor/map/src/components/WelcomeItem.vue +87 -0
  39. ast_monitor/map/src/components/icons/IconCommunity.vue +7 -0
  40. ast_monitor/map/src/components/icons/IconDocumentation.vue +7 -0
  41. ast_monitor/map/src/components/icons/IconEcosystem.vue +7 -0
  42. ast_monitor/map/src/components/icons/IconSupport.vue +7 -0
  43. ast_monitor/map/src/components/icons/IconTooling.vue +19 -0
  44. ast_monitor/map/src/main.ts +25 -0
  45. ast_monitor/map/src/stores/counter.ts +12 -0
  46. ast_monitor/map/tsconfig.app.json +20 -0
  47. ast_monitor/map/tsconfig.json +11 -0
  48. ast_monitor/map/tsconfig.node.json +16 -0
  49. ast_monitor/map/vite.config.ts +18 -0
  50. ast_monitor/model.py +521 -0
  51. ast_monitor/route_reader.py +62 -0
  52. ast_monitor/simulation.py +117 -0
  53. ast_monitor/training_session.py +44 -0
  54. ast_monitor/write_log.py +50 -0
  55. ast_monitor-0.5.0.dist-info/LICENSE +21 -0
  56. ast_monitor-0.5.0.dist-info/METADATA +293 -0
  57. ast_monitor-0.5.0.dist-info/RECORD +58 -0
  58. ast_monitor-0.5.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,30 @@
1
+ from ast_monitor.basic_data import BasicData
2
+ from ast_monitor.digital_twin import DigitalTwin
3
+ from ast_monitor.goals_processor import GoalsProcessor
4
+ from ast_monitor.gps_sensor import GpsSensor
5
+ from ast_monitor.hr_sensor import HrSensor
6
+ from ast_monitor.interval_training import IntervalTraining
7
+ from ast_monitor.mainwindow import Ui_MainWindow
8
+ from ast_monitor.model import AST
9
+ from ast_monitor.route_reader import RouteReader
10
+ from ast_monitor.simulation import Simulation
11
+ from ast_monitor.training_session import TrainingSession
12
+ from ast_monitor.write_log import WriteLog
13
+
14
+ __all__ = [
15
+ 'BasicData',
16
+ 'DigitalTwin',
17
+ 'GpsSensor',
18
+ 'HrSensor',
19
+ 'IntervalTraining',
20
+ 'Ui_MainWindow',
21
+ 'AST',
22
+ 'GoalsProcessor',
23
+ 'RouteReader',
24
+ 'Simulation',
25
+ 'TrainingSession',
26
+ 'WriteLog'
27
+ ]
28
+
29
+ __project__ = 'ast_monitor'
30
+ __version__ = '0.5.0'
@@ -0,0 +1,116 @@
1
+ import math
2
+ import os
3
+ import time
4
+
5
+
6
+ class BasicData:
7
+ """
8
+ Class for storing and tracking the basic training data in real time.\n
9
+ Args:
10
+ hr_data_path (str):
11
+ path to the file that contains HR data
12
+ gps_data_path (str):
13
+ path to the file that contains GPS data
14
+ """
15
+ def __init__(self, hr_data_path: str, gps_data_path: str) -> None:
16
+ """
17
+ Initialization method for BasicData class.\n
18
+ Args:
19
+ hr_data_path (str):
20
+ path to the file that contains HR data
21
+ gps_data_path (str):
22
+ path to the file that contains GPS data
23
+ """
24
+ self.hr_data_path = hr_data_path
25
+ self.gps_data_path = gps_data_path
26
+ self.current_speed = None
27
+ self.current_heart_rate = None
28
+ self.previous_gps = None
29
+ self.current_gps = None
30
+ self.distance = 0.0
31
+
32
+ with open(self.gps_data_path, 'a') as f:
33
+ f.truncate(0)
34
+ with open(self.hr_data_path, 'a') as f:
35
+ f.truncate(0)
36
+
37
+ def read_current_hr(self) -> None:
38
+ """
39
+ Reading the current HR from the file.
40
+ """
41
+ if not os.path.exists(self.hr_data_path):
42
+ self.current_heart_rate = None
43
+ return
44
+
45
+ with open(self.hr_data_path, 'rb') as f:
46
+ try:
47
+ f.seek(-2, os.SEEK_END)
48
+ while f.read(1) != b'\n':
49
+ f.seek(-2, os.SEEK_CUR)
50
+ except OSError:
51
+ f.seek(0)
52
+ try:
53
+ hr = int(f.readline().decode().rstrip())
54
+ self.current_heart_rate = str(hr)
55
+ except ValueError:
56
+ self.current_heart_rate = None
57
+
58
+ def read_current_gps(self) -> None:
59
+ """
60
+ Reading the current GPS position from the file.
61
+ """
62
+ if not os.path.exists(self.gps_data_path):
63
+ self.current_speed = None
64
+ return
65
+
66
+ with open(self.gps_data_path, 'rb') as f:
67
+ try:
68
+ f.seek(-2, os.SEEK_END)
69
+ while f.read(1) != b'\n':
70
+ f.seek(-2, os.SEEK_CUR)
71
+ except OSError:
72
+ f.seek(0)
73
+ try:
74
+ self.previous_gps = self.current_gps
75
+
76
+ # Reading the last GPS data.
77
+ gps = f.readline().decode().rstrip().split(';')
78
+ self.current_gps = (
79
+ float(gps[1]), float(gps[0]), float(gps[2]), float(gps[3])
80
+ )
81
+ except IndexError:
82
+ self.previous_gps = None
83
+ self.current_gps = None
84
+
85
+ def calculate_speed(self) -> None:
86
+ """
87
+ Calculating the speed between the previous and the current trackpoint.
88
+ """
89
+ if not self.previous_gps or not self.current_gps:
90
+ self.current_speed = None
91
+ return
92
+
93
+ # If the last measure is older than 1.5 seconds,
94
+ # it is considered deprecated.
95
+ if time.time() - self.current_gps[3] > 1.5:
96
+ self.current_speed = None
97
+ return
98
+
99
+ # Calculating the distance.
100
+ R = 6373.0
101
+ lat1 = math.radians(self.previous_gps[0])
102
+ lat2 = math.radians(self.current_gps[0])
103
+ lon1 = math.radians(self.previous_gps[1])
104
+ lon2 = math.radians(self.current_gps[1])
105
+ dlon = lon2 - lon1
106
+ dlat = lat2 - lat1
107
+ a = (
108
+ math.sin(dlat / 2)**2 +
109
+ math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
110
+ )
111
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
112
+
113
+ self.distance = 1000 * R * c
114
+ t = self.current_gps[3] - self.previous_gps[3]
115
+ if t:
116
+ self.current_speed = 3.6 * self.distance / t
@@ -0,0 +1,155 @@
1
+ import time
2
+
3
+
4
+ class DigitalTwin:
5
+ """
6
+ Class that represents a digital twin that
7
+ uses a mathematical prediction model to
8
+ analyze an exercise in the real time.\n
9
+ Args:
10
+ predicted_heart_rate (int):
11
+ a predicted heart rate of an interval
12
+ (speed or rest segment) in beats per minute
13
+ duration (int):
14
+ duration of an interval in minutes
15
+ """
16
+ def __init__(
17
+ self,
18
+ proposed_heart_rate: int,
19
+ duration: int,
20
+ tick_time: float,
21
+ basic_data
22
+ ) -> None:
23
+ """
24
+ Initialization method of the DigitalTwin class.\n
25
+ Args:
26
+ predicted_heart_rate (int):
27
+ a predicted heart rate of an interval
28
+ (speed or rest segment) in beats per minute
29
+ duration (int):
30
+ duration of an interval in minutes
31
+ tick_time (float):
32
+ a tick time in seconds
33
+ """
34
+ self.proposed_heart_rate = proposed_heart_rate
35
+ self.predicted_duration = 60 * duration
36
+ self.current_heart_rate = 0
37
+ self.start_time = time.time()
38
+ self.expected_trackpoints = (60 * 1000 * duration) // tick_time
39
+ self.basic_data = basic_data
40
+
41
+ def read_control_data(self) -> tuple:
42
+ """
43
+ Reading the current heart rate and the interval duration.\n
44
+ Returns:
45
+ tuple[int, float]: current heart rate, current duration
46
+ """
47
+ # Reading the current heart rate.
48
+ hr = self.basic_data.current_heart_rate
49
+ if hr:
50
+ hr = int(hr)
51
+
52
+ # Calculating the current phase time.
53
+ phase_time = time.time() - self.start_time
54
+
55
+ return hr, phase_time
56
+
57
+ def calculate_prediction(
58
+ self,
59
+ trimp_delta: float,
60
+ average_heart_rate: float,
61
+ proposed_heart_rate: int,
62
+ time_delta: float,
63
+ expected_trackpoints: int
64
+ ) -> float:
65
+ """
66
+ Calculating the predicted heart rate.\n
67
+ Args:
68
+ trimp_delta (float):
69
+ the calculated TRIMP difference
70
+ average_heart_rate (float):
71
+ the average heart rate of the interval in beats per minute
72
+ proposed_heart_rate (int):
73
+ the proposed heart rate of the interval in beats per minute
74
+ time_delta (float):
75
+ the time difference in seconds
76
+ expected_trackpoints (int):
77
+ the number of expected trackpoints
78
+ Returns:
79
+ float: the proposed heart rate
80
+ """
81
+ if trimp_delta < 0:
82
+ self.minus += trimp_delta
83
+ else:
84
+ self.plus += trimp_delta
85
+
86
+ if average_heart_rate < proposed_heart_rate:
87
+ return proposed_heart_rate
88
+ else:
89
+ return (
90
+ proposed_heart_rate +
91
+ (self.minus - self.plus) / (expected_trackpoints * time_delta)
92
+ )
93
+
94
+ def predict_heart_rate(self) -> None:
95
+ """
96
+ Digital twin algorithm that monitors the athlete
97
+ performance in the real time and calculates the
98
+ predicted heart rate.
99
+ """
100
+ n = 1
101
+ current_time = 0
102
+ current_heart_rate = 0
103
+ previous_heart_rate, previous_time = self.read_control_data()
104
+ self.average_heart_rate = previous_heart_rate
105
+ time_delta = 0
106
+ self.minus, self.plus = 0, 0
107
+
108
+ while current_time <= self.predicted_duration - 1:
109
+ # Reading the current heart rate and the duration.
110
+ current_heart_rate, current_time = self.read_control_data()
111
+
112
+ time_delta = current_time - previous_time
113
+ if time_delta == 0:
114
+ continue
115
+
116
+ if self.predicted_duration - 1 <= current_time:
117
+ break
118
+
119
+ if current_heart_rate and previous_heart_rate:
120
+ # Calculating the average heart rate and the time difference.
121
+ if self.average_heart_rate:
122
+ self.average_heart_rate = (
123
+ self.average_heart_rate +
124
+ (1 / n) * (
125
+ current_heart_rate - self.average_heart_rate
126
+ )
127
+ )
128
+ else:
129
+ self.average_heart_rate = current_heart_rate
130
+
131
+ # Calculating the TRIMP difference.
132
+ trimp_delta = (
133
+ ((current_heart_rate - previous_heart_rate) / 2) *
134
+ time_delta -
135
+ self.proposed_heart_rate * time_delta
136
+ )
137
+
138
+ # Predicted heart rate calculation.
139
+ self.predicted_heart_rate = self.calculate_prediction(
140
+ trimp_delta,
141
+ self.average_heart_rate,
142
+ self.proposed_heart_rate,
143
+ self.predicted_duration - current_time,
144
+ self.expected_trackpoints
145
+ )
146
+ if self.predicted_heart_rate:
147
+ self.predicted_heart_rate = round(
148
+ self.predicted_heart_rate
149
+ )
150
+
151
+ n += 1
152
+
153
+ previous_time = current_time
154
+ previous_heart_rate = current_heart_rate
155
+ time.sleep(0.5)
@@ -0,0 +1,106 @@
1
+ import math
2
+
3
+
4
+ class GoalsProcessor:
5
+ def __init__(self, route):
6
+ """Class for measuring the progress of the user on the route."""
7
+ self.position = None
8
+ self.total_ascent = route["evaluation"]["total_ascent"]
9
+ self.total_distance = route["evaluation"]["total_distance"]
10
+ self.ascents = route["ascents"]
11
+ self.distances = route["distances"]
12
+ self.current_distance = 0
13
+ self.current_ascent = 0
14
+ self.progress = 0
15
+ self.distance_to_go = sum(self.distances)
16
+ self.ascent_to_go = sum(self.ascents)
17
+ self.index_closest = None
18
+ self.route = route
19
+ filteredIntersections = [sublist[1] for sublist in route["nodes"]]
20
+
21
+ # Filter the list of dictionaries based on the IDs and remove duplicates
22
+ filtered_list = []
23
+ last_added_id = None
24
+
25
+ for obj in route['route_render']:
26
+ # Check if the id is in filteredIntersections
27
+ if obj['id'] in filteredIntersections:
28
+ # Check if the id is the same as the last added id
29
+ if obj['id'] != last_added_id:
30
+ # Add the object to the filtered list
31
+ filtered_list.append(obj)
32
+
33
+ # Update the last added id
34
+ last_added_id = obj['id']
35
+
36
+ # Now filtered_list contains the filtered and unique dictionaries
37
+ self.filtered_list = filtered_list
38
+ a = 100
39
+
40
+ def add_position(self, position):
41
+ """Add the current position of the user."""
42
+ self.position = position[0:2]
43
+ self.get_progress(self.position)
44
+
45
+ def get_progress(self, position):
46
+ """Calculate the progress of the user on the route, (%) of route, Ascent, Distance."""
47
+ current_node = self.find_closest_object(position)
48
+ distance = self.haversine_distance(position, (current_node['lat'], current_node['lon']))
49
+ if distance < 0.05:
50
+ self.current_distance += sum(self.distances[0:self.index_closest + 1])
51
+ self.current_ascent += sum(self.ascents[0:self.index_closest + 1])
52
+ # remove the summed indexes
53
+ self.distances = self.distances[self.index_closest + 1:]
54
+ self.ascents = self.ascents[self.index_closest + 1:]
55
+ self.filtered_list = self.filtered_list[self.index_closest + 1:]
56
+ self.index_closest = None
57
+ self.distance_to_go = self.total_distance - self.current_distance
58
+ self.ascent_to_go = self.total_ascent - self.current_ascent
59
+ self.progress = self.current_distance / self.total_distance
60
+
61
+ def find_closest_object(self, position):
62
+ """Finds the closest object in the list to the given point."""
63
+ smallest_diff = float('inf')
64
+ closest_object = None
65
+
66
+ index = 0
67
+ # Loop through each object in the list
68
+ for obj in self.filtered_list:
69
+ lat_diff = math.fabs(obj['lat'] - position[0])
70
+ lon_diff = math.fabs(obj['lon'] - position[1])
71
+
72
+ # Calculate the total difference for both latitude and longitude
73
+ total_diff = lat_diff + lon_diff
74
+
75
+ # Update smallest_diff and closest_object if this object is closer to the given point
76
+ if total_diff < smallest_diff:
77
+ smallest_diff = total_diff
78
+ closest_object = obj
79
+ self.index_closest = index
80
+ index += 1
81
+
82
+ return closest_object # Return the closest object
83
+
84
+ def haversine_distance(self, coord1, coord2):
85
+ """
86
+ Calculate the Haversine distance between two points on the Earth specified by latitude/longitude.
87
+ """
88
+
89
+ # Radius of Earth in kilometers
90
+ R = 6371.0
91
+
92
+ # Convert latitude and longitude from degrees to radians
93
+ lat1, lon1 = math.radians(coord1[0]), math.radians(coord1[1])
94
+ lat2, lon2 = math.radians(coord2[0]), math.radians(coord2[1])
95
+
96
+ # Compute differences in latitude and longitude
97
+ dlat = lat2 - lat1
98
+ dlon = lon2 - lon1
99
+
100
+ # Haversine formula
101
+ a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
102
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
103
+
104
+ distance = R * c
105
+
106
+ return distance
@@ -0,0 +1,81 @@
1
+ import time
2
+
3
+ # It should work on ARM architectures only.
4
+ try:
5
+ import adafruit_gps
6
+ import serial
7
+ except BaseException:
8
+ pass
9
+
10
+
11
+ class GpsSensor:
12
+ """
13
+ Class for working with GPS sensor.\n
14
+ Args:
15
+ gps_path (str):
16
+ path to file for storing GPS data
17
+ """
18
+ def __init__(self, gps_path: str = 'sensor_data/gps.txt') -> None:
19
+ """
20
+ Initialisation method for GpsSensor class.\n
21
+ Args:
22
+ gps_path (str):
23
+ path to file for storing gps data
24
+ """
25
+ self.gps_path = gps_path
26
+
27
+ def write_gps_data_to_file(
28
+ self,
29
+ longitude: float,
30
+ latitude: float,
31
+ altitude: float
32
+ ) -> None:
33
+ """
34
+ Method for writing GPS data to text file.\n
35
+ Args:
36
+ longitude (float):
37
+ longitude on Earth
38
+ latitude (float):
39
+ latitude on Earth
40
+ altitude (float):
41
+ current altitude
42
+ """
43
+ output = (
44
+ str(longitude) + ';' + str(latitude) + ';' +
45
+ str(altitude) + ';' + time.time()
46
+ )
47
+ with open(self.gps_path, 'a') as f:
48
+ f.write(output + '\n')
49
+
50
+ def get_gps_data(self) -> None:
51
+ """
52
+ Method for listening the channel for obtaining GPS data from sensor.\n
53
+ Note: Example is based on source code from
54
+ https://github.com/adafruit/Adafruit_CircuitPython_GPS
55
+ """
56
+ uart = serial.Serial('/dev/serial0', baudrate=9600, timeout=10)
57
+ gps = adafruit_gps.GPS(uart, debug=False)
58
+ gps.send_command(b'PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
59
+ gps.send_command(b'PMTK220,1000')
60
+ last_print = time.monotonic()
61
+
62
+ while True:
63
+ gps.update()
64
+
65
+ current = time.monotonic()
66
+ if current - last_print >= 1.0:
67
+ last_print = current
68
+ if not gps.has_fix:
69
+ print('Waiting for fix...')
70
+ continue
71
+
72
+ LATITUDE = gps.latitude
73
+ LONGITUDE = gps.longitude
74
+ ALTITUDE = gps.altitude_m
75
+
76
+ if (
77
+ (LATITUDE is not None) and
78
+ (LONGITUDE is not None) and
79
+ (ALTITUDE is not None)
80
+ ):
81
+ self.write_gps_data_to_file(LONGITUDE, LATITUDE, ALTITUDE)
@@ -0,0 +1,71 @@
1
+ # If openant dependency is missing.
2
+ try:
3
+ from ant.easy.node import Node
4
+ from ant.easy.channel import Channel
5
+ except BaseException:
6
+ pass
7
+
8
+
9
+ class HrSensor():
10
+ """
11
+ Class for working with HR sensor.\n
12
+ Args:
13
+ hr_path (str):
14
+ path to file for storing HR data
15
+ """
16
+ def __init__(self, hr_path='sensor_data/hr.txt') -> None:
17
+ """
18
+ Initialisation method for HrSensor class.\n
19
+ Args:
20
+ hr_path (str):
21
+ path to file for storing HR data
22
+ """
23
+ self.hr_path = hr_path
24
+
25
+ def write_hr_data_to_file(self, hr: int) -> None:
26
+ """
27
+ Method for writing hr data to text file.\n
28
+ Args:
29
+ hr (int):
30
+ heart rate
31
+ """
32
+ with open(self.hr_path, 'a') as f:
33
+ f.write(hr + "\n")
34
+
35
+ def on_data(self, data) -> None:
36
+ """
37
+ Extracting and writing heart rate data to file.\n
38
+ Args:
39
+ data ():
40
+ list that contains heart rate
41
+ """
42
+ heartrate = data[7]
43
+ print(heartrate)
44
+ self.write_hr_data_to_file(str(heartrate))
45
+
46
+ def get_hr_data(self) -> None:
47
+ """
48
+ Method for listening the channel for obtaining HR data from sensor.\n
49
+ Note: Example is based on source code from
50
+ https://github.com/Tigge/openant/blob/master/examples/heart_rate_monitor.py
51
+ """
52
+ NETWORK_KEY = [0xB9, 0xA5, 0x21, 0xFB, 0xBD, 0x72, 0xC3, 0x45]
53
+
54
+ node = Node()
55
+ node.set_network_key(0x00, NETWORK_KEY)
56
+
57
+ channel = node.new_channel(Channel.Type.BIDIRECTIONAL_RECEIVE)
58
+
59
+ channel.on_broadcast_data = self.on_data
60
+ channel.on_burst_data = self.on_data
61
+
62
+ channel.set_period(8070)
63
+ channel.set_search_timeout(12)
64
+ channel.set_rf_freq(57)
65
+ channel.set_id(0, 120, 0)
66
+
67
+ try:
68
+ channel.open()
69
+ node.start()
70
+ finally:
71
+ node.stop()
@@ -0,0 +1,24 @@
1
+ import http.server
2
+ import os
3
+ from urllib.parse import urlparse
4
+
5
+
6
+ class CustomHandler(http.server.SimpleHTTPRequestHandler):
7
+ """Custom handler to serve the html files from the dist folder instead of the root folder.
8
+ The handler helps us avoid changing the current directory of the Python script."""
9
+
10
+ def translate_path(self, path):
11
+ """
12
+ Method that allows the server to serve the html files from the dist folder instead of the root folder.
13
+ Args:
14
+ path: url path
15
+
16
+ Returns:
17
+
18
+ """
19
+ parsed_path = urlparse(path).path
20
+ current_file_directory = os.path.dirname(os.path.abspath(__file__))
21
+ custom_folder = os.path.join(current_file_directory, 'map', 'dist')
22
+ translated_path = os.path.join(custom_folder, parsed_path.lstrip('/'))
23
+ print(f"Translated path: {translated_path}") # Debugging line to print the translated path
24
+ return translated_path
@@ -0,0 +1,61 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+ xmlns:cc="http://creativecommons.org/ns#"
5
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+ xmlns:svg="http://www.w3.org/2000/svg"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
+ width="6.5972247mm"
11
+ height="8.3541622mm"
12
+ viewBox="0 0 6.5972248 8.354162"
13
+ version="1.1"
14
+ id="svg1844"
15
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
16
+ sodipodi:docname="arrow_down.svg">
17
+ <defs
18
+ id="defs1838" />
19
+ <sodipodi:namedview
20
+ id="base"
21
+ pagecolor="#ffffff"
22
+ bordercolor="#666666"
23
+ borderopacity="1.0"
24
+ inkscape:pageopacity="0.0"
25
+ inkscape:pageshadow="2"
26
+ inkscape:zoom="11.2"
27
+ inkscape:cx="-1.0870781"
28
+ inkscape:cy="16.256367"
29
+ inkscape:document-units="mm"
30
+ inkscape:current-layer="layer1"
31
+ inkscape:document-rotation="0"
32
+ showgrid="false"
33
+ inkscape:window-width="1920"
34
+ inkscape:window-height="1017"
35
+ inkscape:window-x="-8"
36
+ inkscape:window-y="-8"
37
+ inkscape:window-maximized="1" />
38
+ <metadata
39
+ id="metadata1841">
40
+ <rdf:RDF>
41
+ <cc:Work
42
+ rdf:about="">
43
+ <dc:format>image/svg+xml</dc:format>
44
+ <dc:type
45
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
46
+ <dc:title></dc:title>
47
+ </cc:Work>
48
+ </rdf:RDF>
49
+ </metadata>
50
+ <g
51
+ inkscape:label="Layer 1"
52
+ inkscape:groupmode="layer"
53
+ id="layer1"
54
+ transform="translate(-109.33837,-90.883931)">
55
+ <path
56
+ style="fill:#5bad5e;fill-opacity:1;stroke:#000000;stroke-width:0.19538px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
57
+ d="m 114.5562,91.037913 c 10e-4,0.05431 0,4.250421 0,4.250421 0,0 0.85295,0.01977 1.27904,0 0.11267,-0.0052 -3.07792,3.854675 -3.19759,3.852091 -0.11723,-0.0025 -3.33164,-3.850198 -3.19759,-3.852091 0.42632,-0.0059 1.27903,0 1.27903,0 0,0 2.7e-4,-4.166126 0,-4.250421 -2.5e-4,-0.07785 3.83576,-0.07221 3.83711,0 z"
58
+ id="path1484-2"
59
+ sodipodi:nodetypes="scssscss" />
60
+ </g>
61
+ </svg>