imagebaker 0.0.41__py3-none-any.whl → 0.0.48__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 (42) hide show
  1. imagebaker/__init__.py +1 -1
  2. imagebaker/core/__init__.py +0 -0
  3. imagebaker/core/configs/__init__.py +1 -0
  4. imagebaker/core/configs/configs.py +156 -0
  5. imagebaker/core/defs/__init__.py +1 -0
  6. imagebaker/core/defs/defs.py +258 -0
  7. imagebaker/core/plugins/__init__.py +0 -0
  8. imagebaker/core/plugins/base_plugin.py +39 -0
  9. imagebaker/core/plugins/cosine_plugin.py +39 -0
  10. imagebaker/layers/__init__.py +3 -0
  11. imagebaker/layers/annotable_layer.py +847 -0
  12. imagebaker/layers/base_layer.py +724 -0
  13. imagebaker/layers/canvas_layer.py +1007 -0
  14. imagebaker/list_views/__init__.py +3 -0
  15. imagebaker/list_views/annotation_list.py +203 -0
  16. imagebaker/list_views/canvas_list.py +185 -0
  17. imagebaker/list_views/image_list.py +138 -0
  18. imagebaker/list_views/layer_list.py +390 -0
  19. imagebaker/list_views/layer_settings.py +219 -0
  20. imagebaker/models/__init__.py +0 -0
  21. imagebaker/models/base_model.py +150 -0
  22. imagebaker/tabs/__init__.py +2 -0
  23. imagebaker/tabs/baker_tab.py +496 -0
  24. imagebaker/tabs/layerify_tab.py +837 -0
  25. imagebaker/utils/__init__.py +0 -0
  26. imagebaker/utils/image.py +105 -0
  27. imagebaker/utils/state_utils.py +92 -0
  28. imagebaker/utils/transform_mask.py +107 -0
  29. imagebaker/window/__init__.py +1 -0
  30. imagebaker/window/app.py +136 -0
  31. imagebaker/window/main_window.py +181 -0
  32. imagebaker/workers/__init__.py +3 -0
  33. imagebaker/workers/baker_worker.py +247 -0
  34. imagebaker/workers/layerify_worker.py +91 -0
  35. imagebaker/workers/model_worker.py +54 -0
  36. {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/METADATA +6 -6
  37. imagebaker-0.0.48.dist-info/RECORD +41 -0
  38. {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/WHEEL +1 -1
  39. imagebaker-0.0.41.dist-info/RECORD +0 -7
  40. {imagebaker-0.0.41.dist-info/licenses → imagebaker-0.0.48.dist-info}/LICENSE +0 -0
  41. {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/entry_points.txt +0 -0
  42. {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,724 @@
1
+ from imagebaker.core.configs import LayerConfig, CanvasConfig, CursorDef
2
+ from imagebaker.core.defs import Annotation, MouseMode, LayerState, DrawingState
3
+ from imagebaker.utils.state_utils import calculate_intermediate_states
4
+ from imagebaker import logger
5
+
6
+ from PySide6.QtCore import QPointF, QPoint, Qt, Signal, QSizeF, QSize
7
+ from PySide6.QtGui import (
8
+ QColor,
9
+ QPixmap,
10
+ QPainter,
11
+ QMouseEvent,
12
+ QKeyEvent,
13
+ QImage,
14
+ QPen,
15
+ QCursor,
16
+ )
17
+ from PySide6.QtWidgets import QWidget
18
+
19
+ from typing import Optional
20
+ from pathlib import Path
21
+
22
+
23
+ class BaseLayer(QWidget):
24
+ messageSignal = Signal(str)
25
+ zoomChanged = Signal(float)
26
+ mouseMoved = Signal(QPointF)
27
+ annotationCleared = Signal()
28
+ layerRemoved = Signal(int)
29
+ layersChanged = Signal()
30
+ layerSignal = Signal(object)
31
+
32
+ def __init__(self, parent: QWidget, config: LayerConfig | CanvasConfig):
33
+ """
34
+ BaseLayer is an abstract class that represents a single layer in the canvas.
35
+ It provides functionality for managing layer properties, handling user interactions,
36
+ and rendering the layer's content. This class is designed to be extended by
37
+ subclasses that implement specific layer behaviors.
38
+
39
+ Attributes:
40
+ id (int): Unique identifier for the layer.
41
+ layer_state (LayerState): The current state of the layer, including properties
42
+ like position, scale, rotation, and visibility.
43
+ previous_state (LayerState): The previous state of the layer, used for undo operations.
44
+ layers (list[BaseLayer]): A list of child layers associated with this layer.
45
+ annotations (list[Annotation]): A list of annotations associated with the layer.
46
+ selected_annotation (Optional[Annotation]): The currently selected annotation.
47
+ current_annotation (Optional[Annotation]): The annotation currently being created or edited.
48
+ copied_annotation (Optional[Annotation]): A copied annotation for pasting.
49
+ image (QPixmap): The image associated with the layer.
50
+ scale (float): The current scale of the layer.
51
+ pan_offset (QPointF): The current pan offset of the layer.
52
+ mouse_mode (MouseMode): The current mouse interaction mode (e.g., DRAW, PAN, IDLE).
53
+ states (dict[int, list[LayerState]]): A dictionary of saved states for the layer,
54
+ indexed by step.
55
+ current_step (int): The current step in the layer's state history.
56
+ drawing_color (QColor): The color used for drawing operations.
57
+ brush_size (int): The size of the brush used for drawing operations.
58
+ config (LayerConfig | CanvasConfig): Configuration settings for the layer.
59
+ file_path (Path): The file path associated with the layer's image.
60
+ visible (bool): Whether the layer is visible.
61
+ selected (bool): Whether the layer is selected.
62
+ opacity (float): The opacity of the layer (0.0 to 1.0).
63
+ transform_origin (QPointF): The origin point for transformations (e.g., rotation, scaling).
64
+ playing (bool): Whether the layer is currently in a "playing" state (e.g., animation).
65
+ allow_annotation_export (bool): Whether annotations can be exported for this layer.
66
+
67
+ Signals:
68
+ messageSignal (str): Emitted when a message needs to be displayed.
69
+ zoomChanged (float): Emitted when the zoom level changes.
70
+ mouseMoved (QPointF): Emitted when the mouse moves over the layer.
71
+ annotationCleared (): Emitted when annotations are cleared.
72
+ layerRemoved (int): Emitted when a layer is removed.
73
+ layersChanged (): Emitted when the layer list changes.
74
+ layerSignal (object): Emitted with a layer-related signal.
75
+
76
+ Methods:
77
+ save_current_state(steps: int = 1):
78
+ Save the current state of the layer, including intermediate states
79
+ calculated between the previous and current states.
80
+
81
+ set_image(image_path: Path | QPixmap | QImage):
82
+ Set the image for the layer from a file path, QPixmap, or QImage.
83
+
84
+ get_layer(id: str) -> "BaseLayer":
85
+ Retrieve a child layer by its ID.
86
+
87
+ reset_view():
88
+ Reset the view of the layer, including scale and offset.
89
+
90
+ clear_annotations():
91
+ Clear all annotations associated with the layer.
92
+
93
+ update_cursor():
94
+ Update the cursor based on the current mouse mode.
95
+
96
+ undo():
97
+ Undo the last change to the layer's state.
98
+
99
+ set_mode(mode: MouseMode):
100
+ Set the mouse interaction mode for the layer.
101
+
102
+ widget_to_image_pos(pos: QPointF) -> QPointF:
103
+ Convert a widget position to an image position.
104
+
105
+ get_thumbnail(annotation: Annotation = None) -> QPixmap:
106
+ Generate a thumbnail for the layer or a specific annotation.
107
+
108
+ copy() -> "BaseLayer":
109
+ Create a copy of the layer, including its properties and annotations.
110
+
111
+ paintEvent(event):
112
+ Handle the paint event for the layer.
113
+
114
+ paint_layer(painter: QPainter):
115
+ Abstract method to paint the layer's content. Must be implemented by subclasses.
116
+
117
+ handle_mouse_press(event: QMouseEvent):
118
+ Abstract method to handle mouse press events. Must be implemented by subclasses.
119
+
120
+ handle_mouse_move(event: QMouseEvent):
121
+ Abstract method to handle mouse move events. Must be implemented by subclasses.
122
+
123
+ handle_mouse_release(event: QMouseEvent):
124
+ Abstract method to handle mouse release events. Must be implemented by subclasses.
125
+
126
+ handle_wheel(event: QWheelEvent):
127
+ Abstract method to handle wheel events. Must be implemented by subclasses.
128
+
129
+ handle_key_press(event: QKeyEvent):
130
+ Abstract method to handle key press events. Must be implemented by subclasses.
131
+
132
+ handle_key_release(event: QKeyEvent):
133
+ Abstract method to handle key release events. Must be implemented by subclasses.
134
+
135
+ Notes:
136
+ - This class is designed to be extended by subclasses that implement specific
137
+ layer behaviors (e.g., drawing, annotation, image manipulation).
138
+ - The `paint_layer` method must be implemented by subclasses to define how
139
+ the layer's content is rendered.
140
+
141
+ """
142
+ super().__init__(parent)
143
+ self.id = id(self)
144
+ self.layer_state = LayerState(layer_id=self.id)
145
+ self._previous_state = None
146
+ self.thumbnails = {}
147
+ self.label_rects = []
148
+ self._last_state = None
149
+ self.config = config
150
+ self.parent_obj = parent
151
+ self.mouse_mode = MouseMode.IDLE
152
+ self.file_path: Path = Path("Runtime")
153
+ self.layersChanged.connect(self.update)
154
+
155
+ self.drag_start: QPointF = None
156
+ self.drag_offset: QPointF = None
157
+ self.offset: QPointF = QPointF(0, 0)
158
+ self.pan_start: QPointF = None
159
+ self.pan_offset: QPointF = None
160
+ self.image = QPixmap()
161
+ self.annotations: list[Annotation] = []
162
+ self.current_annotation: Optional[Annotation] = None
163
+ self.copied_annotation: Optional[Annotation] = None
164
+ self.selected_annotation: Optional[Annotation] = None
165
+
166
+ self.layers: list[BaseLayer] = []
167
+ self.layer_masks = []
168
+ self._back_buffer = QPixmap()
169
+ self.current_label: str = None
170
+ self.current_color: QColor = QColor(255, 255, 255)
171
+
172
+ self.scale = 1.0
173
+ self.pan_offset = QPointF(0, 0)
174
+ self.last_pan_point = None
175
+ self._dragging_layer = None
176
+ self._drag_offset = QPointF(0, 0)
177
+ self._current_hover = None
178
+ self._active_handle = None
179
+ self._transform_start = None
180
+ self._is_panning = False
181
+ self.offset = QPointF(0, 0)
182
+ self.copied_layer: BaseLayer = None
183
+ self.selected_layer: BaseLayer = None
184
+ self.mouse_mode = MouseMode.IDLE
185
+ self.prev_mouse_mode = MouseMode.IDLE
186
+ self.states: dict[str, list[LayerState]] = dict()
187
+
188
+ self.states: dict[int, list[LayerState]] = dict()
189
+ self.previous_state = None
190
+ self.current_step = 0
191
+ self.drawing_color = QColor(Qt.red) # Default drawing color
192
+ self.brush_size = 5 # Default brush size
193
+
194
+ if isinstance(config, LayerConfig):
195
+ self.current_label = self.config.predefined_labels[0].name
196
+ self.current_color = self.config.predefined_labels[0].color
197
+
198
+ self.setMouseTracking(True)
199
+ self.setFocusPolicy(Qt.StrongFocus)
200
+
201
+ def get_layer(self, id: str) -> "BaseLayer":
202
+ """
203
+ Get a child layer by its ID.
204
+
205
+ Args:
206
+ id (str): The ID of the layer to retrieve.
207
+
208
+ Returns:
209
+ Child of BaseLayer: The child layer with the specified ID, or None if not found
210
+ """
211
+ for layer in self.layers:
212
+ if layer.layer_id == id:
213
+ return layer
214
+ return None
215
+
216
+ def save_current_state(self, steps: int = 1):
217
+ """
218
+ Save the current state of the layer, including intermediate states
219
+ calculated between the previous and current states.
220
+
221
+ Args:
222
+ steps (int): The number of intermediate steps to calculate between
223
+
224
+ Returns:
225
+ None
226
+ """
227
+ curr_states = {}
228
+ mode = self.mouse_mode
229
+
230
+ for layer in self.layers:
231
+ # Calculate intermediate states between previous_state and current_state
232
+ intermediate_states = calculate_intermediate_states(
233
+ layer.previous_state, layer.layer_state.copy(), steps
234
+ )
235
+ is_selected = layer.selected
236
+
237
+ for step, state in enumerate(intermediate_states):
238
+ step += self.current_step
239
+
240
+ logger.info(f"Saving state {step} for layer {layer.layer_id}")
241
+ state.selected = False
242
+ if step not in curr_states:
243
+ curr_states[step] = []
244
+
245
+ # Deep copy the drawing states to avoid unintended modifications
246
+ state.drawing_states = [
247
+ DrawingState(
248
+ position=d.position,
249
+ color=d.color,
250
+ size=d.size,
251
+ )
252
+ for d in layer.layer_state.drawing_states
253
+ ]
254
+ curr_states[step].append(state)
255
+
256
+ # Update the layer's previous_state to the current state
257
+ layer.previous_state = layer.layer_state.copy()
258
+ layer.selected = is_selected
259
+
260
+ # Save the calculated states in self.states
261
+ for step, states in curr_states.items():
262
+ self.states[step] = states
263
+ self.current_step = step
264
+
265
+ # Save the current layer's state
266
+ self.previous_state = self.layer_state.copy()
267
+ self.layer_state.drawing_states = [
268
+ DrawingState(
269
+ position=d.position,
270
+ color=d.color,
271
+ size=d.size,
272
+ )
273
+ for d in self.layer_state.drawing_states
274
+ ]
275
+
276
+ # Emit a message signal indicating the state has been saved
277
+ self.messageSignal.emit(f"Saved state {self.current_step}")
278
+ self.mouse_mode = mode
279
+
280
+ self.update()
281
+
282
+ def minimumSizeHint(self):
283
+ """Return the minimum size hint for the widget."""
284
+ return QSize(100, 100)
285
+
286
+ def widget_to_image_pos(self, pos: QPointF) -> QPointF:
287
+ """
288
+ Convert a widget position to an image position.
289
+ """
290
+ return QPointF(
291
+ (pos.x() - self.offset.x()) / self.scale,
292
+ (pos.y() - self.offset.y()) / self.scale,
293
+ )
294
+
295
+ def update_cursor(self):
296
+ """
297
+ Update the cursor based on the current mouse mode.
298
+ """
299
+ if MouseMode.POINT == self.mouse_mode:
300
+ self.setCursor(CursorDef.POINT_CURSOR)
301
+ elif MouseMode.RECTANGLE == self.mouse_mode:
302
+ self.setCursor(CursorDef.RECTANGLE_CURSOR)
303
+ elif MouseMode.POLYGON == self.mouse_mode:
304
+ self.setCursor(CursorDef.POLYGON_CURSOR)
305
+ elif MouseMode.PAN == self.mouse_mode:
306
+ self.setCursor(CursorDef.PAN_CURSOR)
307
+ elif MouseMode.IDLE == self.mouse_mode:
308
+ self.setCursor(CursorDef.IDLE_CURSOR)
309
+ elif MouseMode.RESIZE == self.mouse_mode:
310
+ self.setCursor(CursorDef.RECTANGLE_CURSOR)
311
+ elif MouseMode.RESIZE_HEIGHT == self.mouse_mode:
312
+ self.setCursor(CursorDef.TRANSFORM_UPDOWN)
313
+ elif MouseMode.RESIZE_WIDTH == self.mouse_mode:
314
+ self.setCursor(CursorDef.TRANSFORM_LEFTRIGHT)
315
+ elif MouseMode.GRAB == self.mouse_mode:
316
+ self.setCursor(CursorDef.GRAB_CURSOR)
317
+ elif self.mouse_mode == MouseMode.DRAW:
318
+ # Create a custom cursor for drawing (circle representing brush size)
319
+ self.setCursor(self._create_custom_cursor(self.drawing_color, "circle"))
320
+
321
+ elif self.mouse_mode == MouseMode.ERASE:
322
+ # Create a custom cursor for erasing (square representing eraser size)
323
+ self.setCursor(self._create_custom_cursor(Qt.white, "square"))
324
+
325
+ else:
326
+ # Reset to default cursor
327
+ self.setCursor(Qt.ArrowCursor)
328
+
329
+ def _create_custom_cursor(self, color: QColor, shape: str) -> QCursor:
330
+ """Create a custom cursor with the given color and shape."""
331
+ pixmap = QPixmap(self.brush_size * 2, self.brush_size * 2)
332
+ pixmap.fill(Qt.transparent)
333
+ painter = QPainter(pixmap)
334
+ painter.setRenderHints(QPainter.Antialiasing)
335
+ painter.setPen(QPen(Qt.black, 1)) # Border color for the cursor
336
+ painter.setBrush(color)
337
+
338
+ if shape == "circle":
339
+ painter.drawEllipse(
340
+ pixmap.rect().center(), self.brush_size, self.brush_size
341
+ )
342
+ elif shape == "square":
343
+ painter.drawRect(pixmap.rect().adjusted(1, 1, -1, -1))
344
+
345
+ painter.end()
346
+ return QCursor(pixmap)
347
+
348
+ def set_image(self, image_path: Path | QPixmap | QImage):
349
+ """
350
+ Set the image for the layer from a file path, QPixmap, or QImage.
351
+ """
352
+ if isinstance(image_path, Path):
353
+ self.file_path = image_path
354
+
355
+ if image_path.exists():
356
+ self.image.load(str(image_path))
357
+ self.reset_view()
358
+ self.update()
359
+ elif isinstance(image_path, QPixmap):
360
+ self.image = image_path
361
+ self.reset_view()
362
+ self.update()
363
+ elif isinstance(image_path, QImage):
364
+ self.image = QPixmap.fromImage(image_path)
365
+ self.reset_view()
366
+ self.update()
367
+
368
+ self.original_size = QSizeF(self.image.size()) # Store original size
369
+
370
+ def get_thumbnail(self, annotation: Annotation = None):
371
+ """
372
+ Generate a thumbnail for the layer or a specific annotation.
373
+ """
374
+ image = QPixmap(*self.config.normal_draw_config.thumbnail_size)
375
+ image.fill(Qt.transparent)
376
+
377
+ if annotation:
378
+ if annotation.rectangle:
379
+ image = self.image.copy(annotation.rectangle.toRect())
380
+ elif annotation.polygon:
381
+ image = self.image.copy(annotation.polygon.boundingRect().toRect())
382
+ elif annotation.points:
383
+ # Create a small thumbnail around the point
384
+ thumbnail_size = 100
385
+ thumbnail = QPixmap(thumbnail_size, thumbnail_size)
386
+ thumbnail.fill(Qt.transparent)
387
+ painter = QPainter(thumbnail)
388
+ painter.setRenderHint(QPainter.Antialiasing)
389
+ painter.setBrush(annotation.color)
390
+ painter.setPen(Qt.NoPen)
391
+ painter.drawEllipse(thumbnail.rect().center() + QPoint(-5, -5), 10, 10)
392
+ painter.end()
393
+ image = thumbnail
394
+ else:
395
+ if self.image:
396
+ image = self.image.copy(
397
+ 0, 0, *self.config.normal_draw_config.thumbnail_size
398
+ )
399
+ elif len(self.layers) > 0:
400
+ image = self.layers[0].get_thumbnail()
401
+ else:
402
+ image = QPixmap(*self.config.normal_draw_config.thumbnail_size)
403
+ image.fill(Qt.transparent)
404
+
405
+ return image.scaled(*self.config.normal_draw_config.thumbnail_size)
406
+
407
+ def copy(self):
408
+ """
409
+ Create a copy of the layer, including its properties and annotations.
410
+ Should be overridden by subclasses to copy additional properties.
411
+ """
412
+ layer = self.__class__(self.parent_obj, self.config)
413
+ layer.set_image(self.image)
414
+ layer.annotations = [ann.copy() for ann in self.annotations]
415
+ layer.layers = [layer.copy() for layer in self.layers]
416
+ layer.layer_name = self.layer_name
417
+ layer.position = self.position
418
+ layer.rotation = self.rotation
419
+ layer.scale = self.scale
420
+ layer.scale_x = self.scale_x
421
+ layer.scale_y = self.scale_y
422
+ layer.opacity = self.opacity
423
+ layer.visible = self.visible
424
+ layer.selected = False
425
+ layer.is_annotable = self.is_annotable
426
+ return layer
427
+
428
+ def set_mode(self, mode: MouseMode):
429
+ """
430
+ Set the mouse interaction mode for the layer.
431
+ """
432
+ # Preserve current annotation when changing modes
433
+ if mode == self.mouse_mode:
434
+ return
435
+
436
+ # Only reset if switching to a different annotation mode
437
+ if mode not in [MouseMode.POLYGON, MouseMode.RECTANGLE, MouseMode.POINT]:
438
+ self.current_annotation = None
439
+
440
+ self.mouse_mode = mode
441
+ logger.debug(f"Layer {self.layer_id}: Mode set to {mode}")
442
+ self.update()
443
+
444
+ def mouseDoubleClickEvent(self, event: QMouseEvent):
445
+ # return super().mouseDoubleClickEvent(event)
446
+ pos = event.pos()
447
+ self.handle_mouse_double_click(event, pos)
448
+ self.update()
449
+ super().mouseDoubleClickEvent(event)
450
+
451
+ def mousePressEvent(self, event):
452
+ self.handle_mouse_press(event)
453
+
454
+ self.update()
455
+ super().mousePressEvent(event)
456
+
457
+ def mouseMoveEvent(self, event: QMouseEvent):
458
+
459
+ self.handle_mouse_move(event)
460
+ self.update()
461
+ super().mouseMoveEvent(event)
462
+
463
+ def mouseReleaseEvent(self, event: QMouseEvent):
464
+
465
+ self.handle_mouse_release(event)
466
+ self.update()
467
+ super().mouseReleaseEvent(event)
468
+
469
+ def wheelEvent(self, event):
470
+ self.handle_wheel(event)
471
+ self.update()
472
+ super().wheelEvent(event)
473
+
474
+ def keyPressEvent(self, event: QKeyEvent):
475
+ self.handle_key_press(event)
476
+ self.update()
477
+ super().keyPressEvent(event)
478
+
479
+ def keyReleaseEvent(self, event):
480
+ if self.is_annotable:
481
+ self.handle_key_release(event)
482
+ else:
483
+ self.handle_key_release(event)
484
+ self.update()
485
+
486
+ def handle_mouse_double_click(self, event, pos):
487
+ raise NotImplementedError
488
+
489
+ def handle_mouse_press(self, event):
490
+ """
491
+ Handle mouse press events for selecting layers, initiating transformations,
492
+ or starting drawing/erasing operations.
493
+
494
+ Args:
495
+ event (QMouseEvent): The mouse press event.
496
+ """
497
+ raise NotImplementedError
498
+
499
+ def handle_mouse_move(self, event):
500
+ """
501
+ Handle mouse move events for panning, drawing, erasing, or transforming layers.
502
+
503
+ Args:
504
+ event (QMouseEvent): The mouse move event.
505
+ """
506
+ raise NotImplementedError
507
+
508
+ def handle_mouse_release(self, event):
509
+ """
510
+ Handle mouse release events, such as resetting the active handle or stopping
511
+ drawing/erasing operations.
512
+
513
+ Args:
514
+ event (QMouseEvent): The mouse release event.
515
+ """
516
+ raise NotImplementedError
517
+
518
+ def handle_wheel(self, event):
519
+ """
520
+ Handle mouse wheel events for adjusting the brush size or zooming the canvas.
521
+
522
+ Args:
523
+ event (QWheelEvent): The wheel event.
524
+ """
525
+ raise NotImplementedError
526
+
527
+ def handle_key_press(self, event):
528
+ raise NotImplementedError
529
+
530
+ def handle_key_release(self, event):
531
+ raise NotImplementedError
532
+
533
+ def reset_view(self):
534
+ self.scale = 1.0
535
+ self.offset = QPointF(0, 0)
536
+
537
+ def clear_annotations(self):
538
+ self.annotations.clear()
539
+ self.selected_annotation = None
540
+ self.current_annotation = None
541
+ self.annotationCleared.emit()
542
+ self.update()
543
+
544
+ def paintEvent(self, event):
545
+ self.paint_event()
546
+
547
+ def paint_event(self):
548
+ painter = QPainter(self)
549
+
550
+ painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
551
+
552
+ painter.fillRect(
553
+ self.rect(),
554
+ QColor(
555
+ self.config.normal_draw_config.background_color.red(),
556
+ self.config.normal_draw_config.background_color.green(),
557
+ self.config.normal_draw_config.background_color.blue(),
558
+ ),
559
+ )
560
+ self.paint_layer(painter)
561
+
562
+ def paint_layer(self, painter: QPainter):
563
+ raise NotImplementedError
564
+
565
+ def __del__(self):
566
+ logger.debug(f"Layer {self.layer_id}: {self.layer_name} deleted.")
567
+
568
+ # override update to store last state
569
+ def update(self):
570
+ self._last_state = self.layer_state
571
+ self.update_cursor()
572
+ super().update()
573
+
574
+ def undo(self):
575
+ if self._last_state is not None:
576
+ self.layer_state = self._last_state
577
+ self.update()
578
+
579
+ # Layer ID Property
580
+ @property
581
+ def layer_id(self) -> int:
582
+ return self.layer_state.layer_id
583
+
584
+ @layer_id.setter
585
+ def layer_id(self, value: int):
586
+ self.layer_state.layer_id = value
587
+
588
+ @property
589
+ def is_annotable(self) -> bool:
590
+ return self.layer_state.is_annotable
591
+
592
+ @is_annotable.setter
593
+ def is_annotable(self, value: bool):
594
+ self.layer_state.is_annotable = value
595
+
596
+ # Layer Name Property
597
+ @property
598
+ def layer_name(self) -> str:
599
+ return self.layer_state.layer_name
600
+
601
+ @layer_name.setter
602
+ def layer_name(self, value: str):
603
+ self.layer_state.layer_name = value
604
+
605
+ # Position Property
606
+ @property
607
+ def position(self) -> QPointF:
608
+ return self.layer_state.position
609
+
610
+ @position.setter
611
+ def position(self, value: QPointF):
612
+ self.layer_state.position = value
613
+
614
+ # Rotation Property
615
+ @property
616
+ def rotation(self) -> float:
617
+ return self.layer_state.rotation
618
+
619
+ @rotation.setter
620
+ def rotation(self, value: float):
621
+ self.layer_state.rotation = value
622
+
623
+ # Scale Property
624
+ @property
625
+ def scale(self) -> float:
626
+ return self.layer_state.scale
627
+
628
+ @scale.setter
629
+ def scale(self, value: float):
630
+ self.layer_state.scale = value
631
+
632
+ # Scale X Property
633
+ @property
634
+ def scale_x(self) -> float:
635
+ return self.layer_state.scale_x
636
+
637
+ @scale_x.setter
638
+ def scale_x(self, value: float):
639
+ self.layer_state.scale_x = value
640
+
641
+ # Scale Y Property
642
+ @property
643
+ def scale_y(self) -> float:
644
+ return self.layer_state.scale_y
645
+
646
+ @scale_y.setter
647
+ def scale_y(self, value: float):
648
+ self.layer_state.scale_y = value
649
+
650
+ # Transform Origin Property
651
+ @property
652
+ def transform_origin(self) -> QPointF:
653
+ return self.layer_state.transform_origin
654
+
655
+ @transform_origin.setter
656
+ def transform_origin(self, value: QPointF):
657
+ self.layer_state.transform_origin = value
658
+
659
+ # Order Property
660
+ @property
661
+ def order(self) -> int:
662
+ return self.layer_state.order
663
+
664
+ @order.setter
665
+ def order(self, value: int):
666
+ self.layer_state.order = value
667
+
668
+ # Visibility Property
669
+ @property
670
+ def visible(self) -> bool:
671
+ return self.layer_state.visible
672
+
673
+ @visible.setter
674
+ def visible(self, value: bool):
675
+ self.layer_state.visible = value
676
+
677
+ # Annotation Export Property
678
+ @property
679
+ def allow_annotation_export(self) -> bool:
680
+ return self.layer_state.allow_annotation_export
681
+
682
+ @allow_annotation_export.setter
683
+ def allow_annotation_export(self, value: bool):
684
+ self.layer_state.allow_annotation_export = value
685
+
686
+ @property
687
+ def playing(self) -> bool:
688
+ return self.layer_state.playing
689
+
690
+ @playing.setter
691
+ def playing(self, value: bool):
692
+ self.layer_state.playing = value
693
+
694
+ @property
695
+ def selected(self) -> bool:
696
+ return self.layer_state.selected
697
+
698
+ @selected.setter
699
+ def selected(self, value: bool):
700
+ self.layer_state.selected = value
701
+
702
+ @property
703
+ def opacity(self) -> float:
704
+ return self.layer_state.opacity
705
+
706
+ @opacity.setter
707
+ def opacity(self, value: float):
708
+ self.layer_state.opacity = value
709
+
710
+ @property
711
+ def status(self) -> str:
712
+ return self.layer_state.status
713
+
714
+ @status.setter
715
+ def status(self, value: str):
716
+ self.layer_state.status = value
717
+
718
+ @property
719
+ def drawing_states(self) -> list[DrawingState]:
720
+ return self.layer_state.drawing_states
721
+
722
+ @drawing_states.setter
723
+ def drawing_states(self, value: list[DrawingState]):
724
+ self.layer_state.drawing_states = value