valetudo-map-parser 0.1.9b41__py3-none-any.whl → 0.1.9b42__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.
@@ -0,0 +1,447 @@
1
+ """
2
+ Enhanced Drawable Class.
3
+ Provides drawing utilities with element selection support.
4
+ Version: 0.1.9
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+
11
+ # math is not used in this file
12
+ from typing import Optional, Tuple
13
+
14
+ import numpy as np
15
+
16
+ from .drawable import Drawable
17
+ from .drawable_elements import DrawableElement, DrawingConfig
18
+
19
+
20
+ # Type aliases
21
+ NumpyArray = np.ndarray
22
+ Color = Tuple[int, int, int, int]
23
+
24
+ _LOGGER = logging.getLogger(__name__)
25
+
26
+
27
+ class EnhancedDrawable(Drawable):
28
+ """Enhanced drawing utilities with element selection support."""
29
+
30
+ def __init__(self, drawing_config: Optional[DrawingConfig] = None):
31
+ """Initialize with optional drawing configuration."""
32
+ super().__init__()
33
+ self.drawing_config = drawing_config or DrawingConfig()
34
+
35
+ @staticmethod
36
+ def blend_colors(base_color: Color, overlay_color: Color) -> Color:
37
+ """
38
+ Blend two RGBA colors, considering alpha channels.
39
+
40
+ Args:
41
+ base_color: The base RGBA color
42
+ overlay_color: The overlay RGBA color to blend on top
43
+
44
+ Returns:
45
+ The blended RGBA color
46
+ """
47
+ # Extract components
48
+ r1, g1, b1, a1 = base_color
49
+ r2, g2, b2, a2 = overlay_color
50
+
51
+ # Convert alpha to 0-1 range
52
+ a1 = a1 / 255.0
53
+ a2 = a2 / 255.0
54
+
55
+ # Calculate resulting alpha
56
+ a_out = a1 + a2 * (1 - a1)
57
+
58
+ # Avoid division by zero
59
+ if a_out < 0.0001:
60
+ return (0, 0, 0, 0)
61
+
62
+ # Calculate blended RGB components
63
+ r_out = (r1 * a1 + r2 * a2 * (1 - a1)) / a_out
64
+ g_out = (g1 * a1 + g2 * a2 * (1 - a1)) / a_out
65
+ b_out = (b1 * a1 + b2 * a2 * (1 - a1)) / a_out
66
+
67
+ # Convert back to 0-255 range and return as tuple
68
+ return (
69
+ int(max(0, min(255, r_out))),
70
+ int(max(0, min(255, g_out))),
71
+ int(max(0, min(255, b_out))),
72
+ int(max(0, min(255, a_out * 255))),
73
+ )
74
+
75
+ def blend_pixel(
76
+ self,
77
+ array: NumpyArray,
78
+ x: int,
79
+ y: int,
80
+ color: Color,
81
+ element: DrawableElement,
82
+ element_map: NumpyArray,
83
+ ) -> None:
84
+ """
85
+ Blend a pixel color with the existing color at the specified position.
86
+ Also updates the element map if the new element has higher z-index.
87
+
88
+ Args:
89
+ array: The image array to modify
90
+ x, y: Pixel coordinates
91
+ color: RGBA color to blend
92
+ element: The element being drawn
93
+ element_map: The element map to update
94
+ """
95
+ # Check bounds
96
+ if not (0 <= y < array.shape[0] and 0 <= x < array.shape[1]):
97
+ return
98
+
99
+ # Get current element at this position
100
+ current_element = element_map[y, x]
101
+
102
+ # Get z-indices for comparison
103
+ current_z = (
104
+ self.drawing_config.get_property(current_element, "z_index", 0)
105
+ if current_element
106
+ else 0
107
+ )
108
+ new_z = self.drawing_config.get_property(element, "z_index", 0)
109
+
110
+ # Get current color at this position
111
+ current_color = tuple(array[y, x])
112
+
113
+ # Blend colors
114
+ blended_color = self.blend_colors(current_color, color)
115
+
116
+ # Update pixel color
117
+ array[y, x] = blended_color
118
+
119
+ # Update element map if new element has higher z-index
120
+ if new_z >= current_z:
121
+ element_map[y, x] = element
122
+
123
+ async def draw_map(
124
+ self, map_data: dict, base_array: Optional[NumpyArray] = None
125
+ ) -> Tuple[NumpyArray, NumpyArray]:
126
+ """
127
+ Draw the map with selected elements.
128
+
129
+ Args:
130
+ map_data: The map data dictionary
131
+ base_array: Optional base array to draw on
132
+
133
+ Returns:
134
+ Tuple of (image_array, element_map)
135
+ """
136
+ # Get map dimensions
137
+ size_x = map_data.get("size", {}).get("x", 1024)
138
+ size_y = map_data.get("size", {}).get("y", 1024)
139
+
140
+ # Create empty image if none provided
141
+ if base_array is None:
142
+ background_color = self.drawing_config.get_property(
143
+ DrawableElement.FLOOR, "color", (200, 200, 200, 255)
144
+ )
145
+ base_array = await self.create_empty_image(size_x, size_y, background_color)
146
+
147
+ # Create a 2D map for element identification
148
+ element_map = np.zeros(
149
+ (base_array.shape[0], base_array.shape[1]), dtype=np.int32
150
+ )
151
+
152
+ # Draw elements in order of z-index
153
+ for element in self.drawing_config.get_drawing_order():
154
+ if element == DrawableElement.FLOOR:
155
+ base_array, element_map = await self._draw_floor(
156
+ map_data, base_array, element_map
157
+ )
158
+ elif element == DrawableElement.WALL:
159
+ base_array, element_map = await self._draw_walls(
160
+ map_data, base_array, element_map
161
+ )
162
+ elif element == DrawableElement.ROBOT:
163
+ base_array, element_map = await self._draw_robot(
164
+ map_data, base_array, element_map
165
+ )
166
+ elif element == DrawableElement.CHARGER:
167
+ base_array, element_map = await self._draw_charger(
168
+ map_data, base_array, element_map
169
+ )
170
+ elif element == DrawableElement.VIRTUAL_WALL:
171
+ base_array, element_map = await self._draw_virtual_walls(
172
+ map_data, base_array, element_map
173
+ )
174
+ elif element == DrawableElement.RESTRICTED_AREA:
175
+ base_array, element_map = await self._draw_restricted_areas(
176
+ map_data, base_array, element_map
177
+ )
178
+ elif element == DrawableElement.NO_MOP_AREA:
179
+ base_array, element_map = await self._draw_no_mop_areas(
180
+ map_data, base_array, element_map
181
+ )
182
+ elif element == DrawableElement.PATH:
183
+ base_array, element_map = await self._draw_path(
184
+ map_data, base_array, element_map
185
+ )
186
+ elif element == DrawableElement.PREDICTED_PATH:
187
+ base_array, element_map = await self._draw_predicted_path(
188
+ map_data, base_array, element_map
189
+ )
190
+ elif element == DrawableElement.GO_TO_TARGET:
191
+ base_array, element_map = await self._draw_go_to_target(
192
+ map_data, base_array, element_map
193
+ )
194
+ elif DrawableElement.ROOM_1 <= element <= DrawableElement.ROOM_15:
195
+ room_id = element - DrawableElement.ROOM_1 + 1
196
+ base_array, element_map = await self._draw_room(
197
+ map_data, room_id, base_array, element_map
198
+ )
199
+
200
+ return base_array, element_map
201
+
202
+ async def _draw_floor(
203
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
204
+ ) -> Tuple[NumpyArray, NumpyArray]:
205
+ """Draw the floor layer."""
206
+ if not self.drawing_config.is_enabled(DrawableElement.FLOOR):
207
+ return array, element_map
208
+
209
+ # Implementation depends on the map data format
210
+ # This is a placeholder - actual implementation would use map_data to draw floor
211
+ # For now, we'll just mark the entire map as floor in the element map
212
+ element_map[:] = DrawableElement.FLOOR
213
+
214
+ return array, element_map
215
+
216
+ async def _draw_walls(
217
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
218
+ ) -> Tuple[NumpyArray, NumpyArray]:
219
+ """Draw the walls."""
220
+ if not self.drawing_config.is_enabled(DrawableElement.WALL):
221
+ return array, element_map
222
+
223
+ # Get wall color from drawing config
224
+ wall_color = self.drawing_config.get_property(
225
+ DrawableElement.WALL, "color", (255, 255, 0, 255)
226
+ )
227
+
228
+ # Implementation depends on the map data format
229
+ # For Valetudo maps, we would look at the layers with type "wall"
230
+ # This is a simplified example - in a real implementation, we would extract the actual wall pixels
231
+
232
+ # Find wall data in map_data
233
+ wall_pixels = []
234
+ for layer in map_data.get("layers", []):
235
+ if layer.get("type") == "wall":
236
+ # Extract wall pixels from the layer
237
+ # This is a placeholder - actual implementation would depend on the map data format
238
+ wall_pixels = layer.get("pixels", [])
239
+ break
240
+
241
+ # Draw wall pixels with color blending
242
+ for x, y in wall_pixels:
243
+ # Use blend_pixel to properly blend colors and update element map
244
+ self.blend_pixel(array, x, y, wall_color, DrawableElement.WALL, element_map)
245
+
246
+ return array, element_map
247
+
248
+ async def _draw_robot(
249
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
250
+ ) -> Tuple[NumpyArray, NumpyArray]:
251
+ """Draw the robot."""
252
+ if not self.drawing_config.is_enabled(DrawableElement.ROBOT):
253
+ return array, element_map
254
+
255
+ # Get robot color from drawing config
256
+ robot_color = self.drawing_config.get_property(
257
+ DrawableElement.ROBOT, "color", (255, 255, 204, 255)
258
+ )
259
+
260
+ # Extract robot position and angle from map_data
261
+ robot_position = map_data.get("robot", {}).get("position", None)
262
+ robot_angle = map_data.get("robot", {}).get("angle", 0)
263
+
264
+ if robot_position:
265
+ x, y = robot_position.get("x", 0), robot_position.get("y", 0)
266
+
267
+ # Draw robot with color blending
268
+ # Create a circle around the robot position
269
+ radius = 25 # Same as in the robot drawing method
270
+ for dy in range(-radius, radius + 1):
271
+ for dx in range(-radius, radius + 1):
272
+ if dx * dx + dy * dy <= radius * radius:
273
+ map_x, map_y = int(x + dx), int(y + dy)
274
+ # Use blend_pixel to properly blend colors and update element map
275
+ self.blend_pixel(
276
+ array,
277
+ map_x,
278
+ map_y,
279
+ robot_color,
280
+ DrawableElement.ROBOT,
281
+ element_map,
282
+ )
283
+
284
+ # TODO: Draw robot orientation indicator
285
+ # This would be a line or triangle showing the direction
286
+ # For now, we'll skip this part as it requires more complex drawing
287
+
288
+ return array, element_map
289
+
290
+ async def _draw_charger(
291
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
292
+ ) -> Tuple[NumpyArray, NumpyArray]:
293
+ """Draw the charger."""
294
+ if not self.drawing_config.is_enabled(DrawableElement.CHARGER):
295
+ return array, element_map
296
+
297
+ # Get charger color from drawing config
298
+ charger_color = self.drawing_config.get_property(
299
+ DrawableElement.CHARGER, "color", (255, 128, 0, 255)
300
+ )
301
+
302
+ # Implementation depends on the map data format
303
+ # This would extract charger data from map_data and draw it
304
+
305
+ return array, element_map
306
+
307
+ async def _draw_virtual_walls(
308
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
309
+ ) -> Tuple[NumpyArray, NumpyArray]:
310
+ """Draw virtual walls."""
311
+ if not self.drawing_config.is_enabled(DrawableElement.VIRTUAL_WALL):
312
+ return array, element_map
313
+
314
+ # Get virtual wall color from drawing config
315
+ wall_color = self.drawing_config.get_property(
316
+ DrawableElement.VIRTUAL_WALL, "color", (255, 0, 0, 255)
317
+ )
318
+
319
+ # Implementation depends on the map data format
320
+ # This would extract virtual wall data from map_data and draw it
321
+
322
+ return array, element_map
323
+
324
+ async def _draw_restricted_areas(
325
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
326
+ ) -> Tuple[NumpyArray, NumpyArray]:
327
+ """Draw restricted areas."""
328
+ if not self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
329
+ return array, element_map
330
+
331
+ # Get restricted area color from drawing config
332
+ area_color = self.drawing_config.get_property(
333
+ DrawableElement.RESTRICTED_AREA, "color", (255, 0, 0, 125)
334
+ )
335
+
336
+ # Implementation depends on the map data format
337
+ # This would extract restricted area data from map_data and draw it
338
+
339
+ return array, element_map
340
+
341
+ async def _draw_no_mop_areas(
342
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
343
+ ) -> Tuple[NumpyArray, NumpyArray]:
344
+ """Draw no-mop areas."""
345
+ if not self.drawing_config.is_enabled(DrawableElement.NO_MOP_AREA):
346
+ return array, element_map
347
+
348
+ # Get no-mop area color from drawing config
349
+ area_color = self.drawing_config.get_property(
350
+ DrawableElement.NO_MOP_AREA, "color", (0, 0, 255, 125)
351
+ )
352
+
353
+ # Implementation depends on the map data format
354
+ # This would extract no-mop area data from map_data and draw it
355
+
356
+ return array, element_map
357
+
358
+ async def _draw_path(
359
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
360
+ ) -> Tuple[NumpyArray, NumpyArray]:
361
+ """Draw the robot's path."""
362
+ if not self.drawing_config.is_enabled(DrawableElement.PATH):
363
+ return array, element_map
364
+
365
+ # Get path color from drawing config
366
+ path_color = self.drawing_config.get_property(
367
+ DrawableElement.PATH, "color", (238, 247, 255, 255)
368
+ )
369
+
370
+ # Implementation depends on the map data format
371
+ # This would extract path data from map_data and draw it
372
+
373
+ return array, element_map
374
+
375
+ async def _draw_predicted_path(
376
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
377
+ ) -> Tuple[NumpyArray, NumpyArray]:
378
+ """Draw the predicted path."""
379
+ if not self.drawing_config.is_enabled(DrawableElement.PREDICTED_PATH):
380
+ return array, element_map
381
+
382
+ # Get predicted path color from drawing config
383
+ path_color = self.drawing_config.get_property(
384
+ DrawableElement.PREDICTED_PATH, "color", (238, 247, 255, 125)
385
+ )
386
+
387
+ # Implementation depends on the map data format
388
+ # This would extract predicted path data from map_data and draw it
389
+
390
+ return array, element_map
391
+
392
+ async def _draw_go_to_target(
393
+ self, map_data: dict, array: NumpyArray, element_map: NumpyArray
394
+ ) -> Tuple[NumpyArray, NumpyArray]:
395
+ """Draw the go-to target."""
396
+ if not self.drawing_config.is_enabled(DrawableElement.GO_TO_TARGET):
397
+ return array, element_map
398
+
399
+ # Get go-to target color from drawing config
400
+ target_color = self.drawing_config.get_property(
401
+ DrawableElement.GO_TO_TARGET, "color", (0, 255, 0, 255)
402
+ )
403
+
404
+ # Implementation depends on the map data format
405
+ # This would extract go-to target data from map_data and draw it
406
+
407
+ return array, element_map
408
+
409
+ async def _draw_room(
410
+ self, map_data: dict, room_id: int, array: NumpyArray, element_map: NumpyArray
411
+ ) -> Tuple[NumpyArray, NumpyArray]:
412
+ """Draw a specific room."""
413
+ element = getattr(DrawableElement, f"ROOM_{room_id}")
414
+ if not self.drawing_config.is_enabled(element):
415
+ return array, element_map
416
+
417
+ # Get room color from drawing config
418
+ room_color = self.drawing_config.get_property(
419
+ element,
420
+ "color",
421
+ (135, 206, 250, 255), # Default light blue
422
+ )
423
+
424
+ # Implementation depends on the map data format
425
+ # For Valetudo maps, we would look at the layers with type "segment"
426
+ # This is a simplified example - in a real implementation, we would extract the actual room pixels
427
+
428
+ # Find room data in map_data
429
+ room_pixels = []
430
+ for layer in map_data.get("layers", []):
431
+ if layer.get("type") == "segment" and str(
432
+ layer.get("metaData", {}).get("segmentId")
433
+ ) == str(room_id):
434
+ # Extract room pixels from the layer
435
+ # This is a placeholder - actual implementation would depend on the map data format
436
+ # For example, it might use compressed pixels or other data structures
437
+
438
+ # For demonstration, let's assume we have a list of (x, y) coordinates
439
+ room_pixels = layer.get("pixels", [])
440
+ break
441
+
442
+ # Draw room pixels with color blending
443
+ for x, y in room_pixels:
444
+ # Use blend_pixel to properly blend colors and update element map
445
+ self.blend_pixel(array, x, y, room_color, element, element_map)
446
+
447
+ return array, element_map
@@ -106,6 +106,7 @@ class CameraShared:
106
106
  self.trim_crop_data = None
107
107
  self.trims = TrimsData.from_dict(DEFAULT_VALUES["trims_data"]) # Trims data
108
108
  self.skip_room_ids: List[str] = []
109
+ self.device_info = None # Store the device_info
109
110
 
110
111
  def update_user_colors(self, user_colors):
111
112
  """Update the user colors."""
@@ -187,6 +188,12 @@ class CameraSharedManager:
187
188
  instance = self.get_instance() # Retrieve the correct instance
188
189
 
189
190
  try:
191
+ # Store the device_info in the instance
192
+ instance.device_info = device_info
193
+ _LOGGER.info(
194
+ "%s: Stored device_info in shared instance", instance.file_name
195
+ )
196
+
190
197
  instance.attr_calibration_points = None
191
198
 
192
199
  # Initialize shared data with defaults from DEFAULT_VALUES
@@ -240,6 +247,26 @@ class CameraSharedManager:
240
247
  )
241
248
  instance.trims = TrimsData.from_dict(trim_data)
242
249
 
250
+ # Log disable_obstacles and disable_path settings
251
+ if "disable_obstacles" in device_info:
252
+ _LOGGER.info(
253
+ "%s: device_info contains disable_obstacles: %s",
254
+ instance.file_name,
255
+ device_info["disable_obstacles"],
256
+ )
257
+ if "disable_path" in device_info:
258
+ _LOGGER.info(
259
+ "%s: device_info contains disable_path: %s",
260
+ instance.file_name,
261
+ device_info["disable_path"],
262
+ )
263
+ if "disable_elements" in device_info:
264
+ _LOGGER.info(
265
+ "%s: device_info contains disable_elements: %s",
266
+ instance.file_name,
267
+ device_info["disable_elements"],
268
+ )
269
+
243
270
  except TypeError as ex:
244
271
  _LOGGER.error("Shared data can't be initialized due to a TypeError! %s", ex)
245
272
  except AttributeError as ex:
@@ -16,7 +16,8 @@ from PIL import Image
16
16
 
17
17
  DEFAULT_ROOMS = 1
18
18
 
19
- MY_LOGGER = logging.getLogger(__name__)
19
+ LOGGER = logging.getLogger(__package__)
20
+
20
21
 
21
22
  Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
22
23
  Colors = Dict[str, Color]