imagebaker 0.0.50__py3-none-any.whl → 0.0.53__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.
- imagebaker/core/configs/configs.py +17 -0
- imagebaker/core/defs/defs.py +10 -0
- imagebaker/layers/annotable_layer.py +93 -16
- imagebaker/layers/base_layer.py +9 -1
- imagebaker/layers/canvas_layer.py +29 -6
- imagebaker/list_views/annotation_list.py +4 -2
- imagebaker/list_views/image_list.py +26 -8
- imagebaker/list_views/layer_settings.py +29 -0
- imagebaker/tabs/baker_tab.py +49 -1
- imagebaker/tabs/layerify_tab.py +275 -49
- imagebaker/utils/image.py +9 -6
- imagebaker/utils/state_utils.py +1 -0
- imagebaker/window/main_window.py +77 -6
- imagebaker/workers/baker_worker.py +22 -3
- {imagebaker-0.0.50.dist-info → imagebaker-0.0.53.dist-info}/METADATA +24 -11
- {imagebaker-0.0.50.dist-info → imagebaker-0.0.53.dist-info}/RECORD +20 -20
- {imagebaker-0.0.50.dist-info → imagebaker-0.0.53.dist-info}/LICENSE +0 -0
- {imagebaker-0.0.50.dist-info → imagebaker-0.0.53.dist-info}/WHEEL +0 -0
- {imagebaker-0.0.50.dist-info → imagebaker-0.0.53.dist-info}/entry_points.txt +0 -0
- {imagebaker-0.0.50.dist-info → imagebaker-0.0.53.dist-info}/top_level.txt +0 -0
@@ -74,6 +74,22 @@ class BaseConfig(BaseModel):
|
|
74
74
|
logger.info(f"Created assets folder at {asset_dir}")
|
75
75
|
return asset_dir
|
76
76
|
|
77
|
+
@property
|
78
|
+
def cache_dir(self):
|
79
|
+
cache_dir = self.project_dir / ".imagebaker" / "cache"
|
80
|
+
if not cache_dir.exists():
|
81
|
+
cache_dir.mkdir(parents=True)
|
82
|
+
logger.info(f"Created cache folder at {cache_dir}")
|
83
|
+
return cache_dir
|
84
|
+
|
85
|
+
@property
|
86
|
+
def bake_dir(self):
|
87
|
+
bake_dir = self.project_dir / ".imagebaker" / "bake"
|
88
|
+
if not bake_dir.exists():
|
89
|
+
bake_dir.mkdir(parents=True)
|
90
|
+
logger.info(f"Created bake folder at {bake_dir}")
|
91
|
+
return bake_dir
|
92
|
+
|
77
93
|
class Config:
|
78
94
|
arbitrary_types_allowed = True
|
79
95
|
|
@@ -96,6 +112,7 @@ class LayerConfig(BaseConfig):
|
|
96
112
|
)
|
97
113
|
# whether to search image in subfolders as well
|
98
114
|
full_search: bool = False
|
115
|
+
cleanup_on_exit: bool = False
|
99
116
|
|
100
117
|
def get_label_color(self, label):
|
101
118
|
for lbl in self.predefined_labels:
|
imagebaker/core/defs/defs.py
CHANGED
@@ -78,6 +78,7 @@ class LayerState:
|
|
78
78
|
drawing_states: list[DrawingState] = field(default_factory=list)
|
79
79
|
edge_opacity: int = 100
|
80
80
|
edge_width: int = 10
|
81
|
+
caption: str = ""
|
81
82
|
|
82
83
|
def copy(self):
|
83
84
|
return LayerState(
|
@@ -109,6 +110,7 @@ class LayerState:
|
|
109
110
|
],
|
110
111
|
edge_opacity=self.edge_opacity,
|
111
112
|
edge_width=self.edge_width,
|
113
|
+
caption=self.caption,
|
112
114
|
)
|
113
115
|
|
114
116
|
|
@@ -138,6 +140,7 @@ class Annotation:
|
|
138
140
|
file_path: Path = field(default_factory=lambda: Path("Runtime"))
|
139
141
|
is_model_generated: bool = False
|
140
142
|
model_name: str = None
|
143
|
+
caption: str = ""
|
141
144
|
|
142
145
|
def copy(self):
|
143
146
|
ann = Annotation(
|
@@ -156,6 +159,7 @@ class Annotation:
|
|
156
159
|
file_path=self.file_path,
|
157
160
|
is_model_generated=self.is_model_generated,
|
158
161
|
model_name=self.model_name,
|
162
|
+
caption=self.caption,
|
159
163
|
)
|
160
164
|
ann.is_selected = False
|
161
165
|
return ann
|
@@ -200,6 +204,7 @@ class Annotation:
|
|
200
204
|
"file_path": str(annotation.file_path),
|
201
205
|
"is_model_generated": annotation.is_model_generated,
|
202
206
|
"model_name": annotation.model_name,
|
207
|
+
"caption": annotation.caption,
|
203
208
|
}
|
204
209
|
annotations_dict.append(data)
|
205
210
|
|
@@ -210,6 +215,10 @@ class Annotation:
|
|
210
215
|
def load_from_json(path: str):
|
211
216
|
import json
|
212
217
|
|
218
|
+
# if path does not exist, return empty list
|
219
|
+
if not Path(path).exists():
|
220
|
+
return []
|
221
|
+
|
213
222
|
with open(path, "r") as f:
|
214
223
|
data = json.load(f)
|
215
224
|
|
@@ -230,6 +239,7 @@ class Annotation:
|
|
230
239
|
file_path=Path(d.get("file_path", "Runtime")),
|
231
240
|
is_model_generated=d.get("is_model_generated", False),
|
232
241
|
model_name=d.get("model_name", None),
|
242
|
+
caption=d.get("caption", ""),
|
233
243
|
)
|
234
244
|
|
235
245
|
# Handle points safely
|
@@ -24,6 +24,7 @@ from PySide6.QtGui import (
|
|
24
24
|
QWheelEvent,
|
25
25
|
QMouseEvent,
|
26
26
|
QKeyEvent,
|
27
|
+
QFont,
|
27
28
|
)
|
28
29
|
from PySide6.QtWidgets import (
|
29
30
|
QApplication,
|
@@ -42,6 +43,7 @@ class AnnotableLayer(BaseLayer):
|
|
42
43
|
annotationCleared = Signal()
|
43
44
|
annotationMoved = Signal()
|
44
45
|
layersChanged = Signal()
|
46
|
+
labelUpdated = Signal(tuple)
|
45
47
|
|
46
48
|
def __init__(self, parent, config: LayerConfig, canvas_config: CanvasConfig):
|
47
49
|
super().__init__(parent, config)
|
@@ -67,14 +69,23 @@ class AnnotableLayer(BaseLayer):
|
|
67
69
|
self.annotationCleared.emit()
|
68
70
|
self.update()
|
69
71
|
|
72
|
+
def toggle_annotation_visibility(self):
|
73
|
+
"""Toggle visibility of all annotations."""
|
74
|
+
selected_annotation = self._get_selected_annotation()
|
75
|
+
if selected_annotation is not None:
|
76
|
+
selected_annotation.visible = not selected_annotation.visible
|
77
|
+
self.annotationUpdated.emit(selected_annotation)
|
78
|
+
self.update()
|
79
|
+
|
70
80
|
def handle_key_press(self, event: QKeyEvent):
|
71
81
|
# Handle Ctrl key for panning
|
72
82
|
if event.key() == Qt.Key_Control:
|
73
|
-
|
74
|
-
|
75
|
-
|
83
|
+
# disable this for now to allow pannings
|
84
|
+
# if (
|
85
|
+
# self.mouse_mode != MouseMode.POLYGON
|
86
|
+
# ): # Only activate pan mode when not drawing polygons
|
76
87
|
|
77
|
-
|
88
|
+
self.mouse_mode = MouseMode.PAN
|
78
89
|
|
79
90
|
# Handle Ctrl+C for copy
|
80
91
|
if event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_C:
|
@@ -257,7 +268,7 @@ class AnnotableLayer(BaseLayer):
|
|
257
268
|
|
258
269
|
# Draw labels
|
259
270
|
if annotation.is_complete and annotation.label:
|
260
|
-
painter.save()
|
271
|
+
# painter.save()
|
261
272
|
label_pos = self.get_label_position(annotation)
|
262
273
|
text = annotation.label
|
263
274
|
|
@@ -297,8 +308,58 @@ class AnnotableLayer(BaseLayer):
|
|
297
308
|
# Draw text
|
298
309
|
painter.setPen(Qt.white)
|
299
310
|
painter.drawText(bg_rect, Qt.AlignCenter, text)
|
300
|
-
|
301
|
-
|
311
|
+
|
312
|
+
# now if the annotations has caption,
|
313
|
+
# draw the caption below the label in itallic
|
314
|
+
# just below the label, in a font lighter than a label
|
315
|
+
|
316
|
+
if annotation.caption:
|
317
|
+
caption_font = painter.font()
|
318
|
+
# Draw a background rectangle for the caption to ensure visibility
|
319
|
+
caption_text = annotation.caption
|
320
|
+
caption_font.setItalic(True)
|
321
|
+
caption_font.setPixelSize(
|
322
|
+
self.config.selected_draw_config.label_font_size * self.scale + 2
|
323
|
+
)
|
324
|
+
caption_font.setWeight(QFont.Light)
|
325
|
+
painter.setFont(caption_font)
|
326
|
+
metrics = painter.fontMetrics()
|
327
|
+
text_width = metrics.horizontalAdvance(caption_text)
|
328
|
+
text_height = metrics.height()
|
329
|
+
# Draw background rectangle for caption
|
330
|
+
caption_rect = QRectF(
|
331
|
+
widget_pos.x() - text_width / 2 - 2,
|
332
|
+
widget_pos.y() + text_height / 2 - 2,
|
333
|
+
text_width + 4,
|
334
|
+
text_height + 4,
|
335
|
+
)
|
336
|
+
painter.setPen(Qt.NoPen)
|
337
|
+
painter.setBrush(
|
338
|
+
self.config.normal_draw_config.label_font_background_color
|
339
|
+
)
|
340
|
+
painter.drawRect(caption_rect)
|
341
|
+
# Draw caption text
|
342
|
+
painter.setPen(Qt.white)
|
343
|
+
painter.drawText(caption_rect, Qt.AlignCenter, caption_text)
|
344
|
+
caption_font.setItalic(True)
|
345
|
+
caption_font.setPixelSize(
|
346
|
+
self.config.selected_draw_config.label_font_size * self.scale + 2
|
347
|
+
)
|
348
|
+
caption_font.setWeight(QFont.Light)
|
349
|
+
painter.setFont(caption_font)
|
350
|
+
metrics = painter.fontMetrics()
|
351
|
+
text_width = metrics.horizontalAdvance(annotation.caption)
|
352
|
+
text_height = metrics.height()
|
353
|
+
painter.setPen(Qt.white)
|
354
|
+
painter.setBrush(Qt.gray)
|
355
|
+
|
356
|
+
caption_rect = QRectF(
|
357
|
+
widget_pos.x() - text_width / 2 - 2,
|
358
|
+
widget_pos.y() + text_height / 2 - 2,
|
359
|
+
text_width + 4,
|
360
|
+
text_height + 4,
|
361
|
+
)
|
362
|
+
painter.drawText(caption_rect, Qt.AlignCenter, annotation.caption)
|
302
363
|
|
303
364
|
painter.restore()
|
304
365
|
|
@@ -397,7 +458,14 @@ class AnnotableLayer(BaseLayer):
|
|
397
458
|
MouseMode.ZOOM_IN,
|
398
459
|
MouseMode.ZOOM_OUT,
|
399
460
|
]:
|
400
|
-
self.
|
461
|
+
if self.current_annotation:
|
462
|
+
if (
|
463
|
+
self.current_annotation.polygon
|
464
|
+
and not self.current_annotation.is_complete
|
465
|
+
):
|
466
|
+
self.mouse_mode = MouseMode.POLYGON
|
467
|
+
else:
|
468
|
+
self.mouse_mode = MouseMode.IDLE
|
401
469
|
|
402
470
|
# Clean up transformation state
|
403
471
|
if hasattr(self, "selected_annotation"):
|
@@ -534,13 +602,11 @@ class AnnotableLayer(BaseLayer):
|
|
534
602
|
self.current_annotation.polygon = QPolygonF(
|
535
603
|
[p for p in self.current_annotation.polygon][:-1]
|
536
604
|
)
|
537
|
-
self.update()
|
538
605
|
|
539
606
|
# If the polygon is now empty, reset to idle mode
|
540
607
|
if len(self.current_annotation.polygon) == 0:
|
541
608
|
self.current_annotation = None
|
542
609
|
self.mouse_mode = MouseMode.IDLE
|
543
|
-
self.update()
|
544
610
|
|
545
611
|
# If not drawing a polygon, go to idle mode
|
546
612
|
if not self.current_annotation:
|
@@ -548,7 +614,7 @@ class AnnotableLayer(BaseLayer):
|
|
548
614
|
for ann in self.annotations:
|
549
615
|
ann.selected = False
|
550
616
|
self.annotationUpdated.emit(ann)
|
551
|
-
|
617
|
+
self.update()
|
552
618
|
|
553
619
|
# If left-clicked
|
554
620
|
if event.button() == Qt.LeftButton:
|
@@ -603,6 +669,17 @@ class AnnotableLayer(BaseLayer):
|
|
603
669
|
elif self.mouse_mode == MouseMode.POLYGON:
|
604
670
|
# If not double-click
|
605
671
|
if not self.current_annotation:
|
672
|
+
# if this point is equal to the last point of the previous polygon, then ignore it
|
673
|
+
if len(self.annotations) > 0:
|
674
|
+
last_polygon = self.annotations[-1].polygon
|
675
|
+
if last_polygon:
|
676
|
+
last_point = last_polygon[-1]
|
677
|
+
if last_point == clamped_pos:
|
678
|
+
logger.info(
|
679
|
+
"Ignoring point, same as last polygon point"
|
680
|
+
)
|
681
|
+
return
|
682
|
+
|
606
683
|
self.current_annotation = Annotation(
|
607
684
|
file_path=self.file_path,
|
608
685
|
annotation_id=len(self.annotations),
|
@@ -751,8 +828,8 @@ class AnnotableLayer(BaseLayer):
|
|
751
828
|
and len(self.current_annotation.polygon) >= 3
|
752
829
|
):
|
753
830
|
self.current_annotation.is_complete = True
|
831
|
+
|
754
832
|
self.finalize_annotation()
|
755
|
-
self.annotationAdded.emit(self.current_annotation)
|
756
833
|
self.current_annotation = None
|
757
834
|
|
758
835
|
return
|
@@ -794,6 +871,7 @@ class AnnotableLayer(BaseLayer):
|
|
794
871
|
self, "Edit Label", "Enter new label:", text=annotation.label
|
795
872
|
)
|
796
873
|
if ok and new_label:
|
874
|
+
self.labelUpdated.emit((annotation.label, new_label))
|
797
875
|
annotation.label = new_label
|
798
876
|
self.annotationUpdated.emit(annotation) # Emit signal
|
799
877
|
self.update()
|
@@ -811,8 +889,6 @@ class AnnotableLayer(BaseLayer):
|
|
811
889
|
self.current_annotation
|
812
890
|
)
|
813
891
|
self.annotationAdded.emit(self.current_annotation)
|
814
|
-
self.current_annotation = None
|
815
|
-
self.update()
|
816
892
|
else:
|
817
893
|
# Show custom label dialog
|
818
894
|
label, ok = QInputDialog.getText(self, "Label", "Enter label name:")
|
@@ -827,8 +903,8 @@ class AnnotableLayer(BaseLayer):
|
|
827
903
|
)
|
828
904
|
self.annotationAdded.emit(self.current_annotation)
|
829
905
|
self.current_annotation.annotation_id = len(self.annotations)
|
830
|
-
|
831
|
-
|
906
|
+
self.current_annotation = None
|
907
|
+
self.update()
|
832
908
|
|
833
909
|
# in update, update cursor
|
834
910
|
|
@@ -915,6 +991,7 @@ class AnnotableLayer(BaseLayer):
|
|
915
991
|
new_layer.layer_name = (
|
916
992
|
f"{annotation.label} {annotation.annotation_id} {annotation.annotator}"
|
917
993
|
)
|
994
|
+
new_layer.caption = annotation.caption
|
918
995
|
|
919
996
|
new_layer._apply_edge_opacity()
|
920
997
|
new_layer.update()
|
imagebaker/layers/base_layer.py
CHANGED
@@ -240,7 +240,7 @@ class BaseLayer(QWidget):
|
|
240
240
|
for step, state in enumerate(intermediate_states):
|
241
241
|
step += self.current_step
|
242
242
|
|
243
|
-
logger.info(f"Saving state {step} for layer {layer.layer_id}")
|
243
|
+
logger.info(f"Saving state {step} for layer {layer.layer_id}: {state}")
|
244
244
|
state.selected = False
|
245
245
|
if step not in curr_states:
|
246
246
|
curr_states[step] = []
|
@@ -853,3 +853,11 @@ class BaseLayer(QWidget):
|
|
853
853
|
@edge_width.setter
|
854
854
|
def edge_width(self, value: int):
|
855
855
|
self.layer_state.edge_width = value
|
856
|
+
|
857
|
+
@property
|
858
|
+
def caption(self) -> str:
|
859
|
+
return self.layer_state.caption
|
860
|
+
|
861
|
+
@caption.setter
|
862
|
+
def caption(self, value: str):
|
863
|
+
self.layer_state.caption = value
|
@@ -27,7 +27,6 @@ from PySide6.QtGui import (
|
|
27
27
|
QMouseEvent,
|
28
28
|
QKeyEvent,
|
29
29
|
QTransform,
|
30
|
-
QImage,
|
31
30
|
)
|
32
31
|
from PySide6.QtWidgets import (
|
33
32
|
QApplication,
|
@@ -127,7 +126,6 @@ class CanvasLayer(BaseLayer):
|
|
127
126
|
|
128
127
|
## Helper functions ##
|
129
128
|
def handle_key_press(self, event: QKeyEvent):
|
130
|
-
|
131
129
|
# Handle Delete key
|
132
130
|
if event.key() == Qt.Key_Delete:
|
133
131
|
self._delete_layer()
|
@@ -149,6 +147,15 @@ class CanvasLayer(BaseLayer):
|
|
149
147
|
if event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_V:
|
150
148
|
self._paste_layer()
|
151
149
|
return # Important: return after handling
|
150
|
+
# if event.key() == Qt.Key_H:
|
151
|
+
# if self.selected_layer:
|
152
|
+
# # Toggle visibility of the selected layer
|
153
|
+
# logger.info(
|
154
|
+
# f"Toggling visibility of layer: {self.selected_layer.layer_name}"
|
155
|
+
# )
|
156
|
+
# self.selected_layer.visible = not self.selected_layer.visible
|
157
|
+
# self.selected_layer.update()
|
158
|
+
# return # Important: return after handling
|
152
159
|
|
153
160
|
def paint_layer(self, painter: QPainter):
|
154
161
|
"""
|
@@ -362,7 +369,16 @@ class CanvasLayer(BaseLayer):
|
|
362
369
|
"""
|
363
370
|
"""Add a new drawing state."""
|
364
371
|
self.selected_layer = self._get_selected_layer()
|
365
|
-
layer = self.selected_layer
|
372
|
+
layer = self.selected_layer
|
373
|
+
if not layer:
|
374
|
+
logger.debug("No layer selected for drawing.")
|
375
|
+
# show popup message window that closes in 2 seconds
|
376
|
+
QMessageBox.information(
|
377
|
+
self.parent(),
|
378
|
+
"No Layer Selected",
|
379
|
+
"Please select a layer to draw on.",
|
380
|
+
)
|
381
|
+
return
|
366
382
|
|
367
383
|
# Convert the position to be relative to the layer
|
368
384
|
relative_pos = pos - layer.position
|
@@ -384,8 +400,14 @@ class CanvasLayer(BaseLayer):
|
|
384
400
|
)
|
385
401
|
layer.layer_state.drawing_states.append(drawing_state)
|
386
402
|
self._last_draw_point = relative_pos # Update the last draw point
|
387
|
-
|
388
|
-
|
403
|
+
logger.debug(
|
404
|
+
f"Added drawing state at position: {relative_pos} to layer {layer.layer_name}"
|
405
|
+
)
|
406
|
+
else:
|
407
|
+
logger.debug(
|
408
|
+
f"Mouse mode {self.mouse_mode} does not support drawing states."
|
409
|
+
)
|
410
|
+
return
|
389
411
|
self.update() # Refresh the canvas to show the new drawing
|
390
412
|
|
391
413
|
def handle_wheel(self, event: QWheelEvent):
|
@@ -822,6 +844,7 @@ class CanvasLayer(BaseLayer):
|
|
822
844
|
filename = self.config.export_folder / f"{filename}.png"
|
823
845
|
logger.info(f"Exporting baked image to {filename}")
|
824
846
|
self.states = {0: [layer.layer_state for layer in self.layers]}
|
847
|
+
logger.debug(f"Exporting states: {self.states}")
|
825
848
|
|
826
849
|
self.loading_dialog = QProgressDialog(
|
827
850
|
"Baking Please wait...", "Cancel", 0, 0, self.parentWidget()
|
@@ -1030,7 +1053,7 @@ class CanvasLayer(BaseLayer):
|
|
1030
1053
|
logger.info(f"Saved baked image to {filename}")
|
1031
1054
|
if self.config.write_annotations:
|
1032
1055
|
image = qpixmap_to_numpy(image.copy())
|
1033
|
-
image = cv2.cvtColor(image, cv2.
|
1056
|
+
image = cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA)
|
1034
1057
|
drawn = draw_annotations(image, annotations)
|
1035
1058
|
write_to = filename.parent / f"annotated_{filename.name}"
|
1036
1059
|
|
@@ -52,6 +52,7 @@ class AnnotationList(QDockWidget):
|
|
52
52
|
if self.layer is None:
|
53
53
|
return
|
54
54
|
for idx, ann in enumerate(self.layer.annotations):
|
55
|
+
|
55
56
|
item = QListWidgetItem(self.list_widget)
|
56
57
|
|
57
58
|
# Create container widget
|
@@ -200,5 +201,6 @@ class AnnotationList(QDockWidget):
|
|
200
201
|
self.layer.paste_annotation()
|
201
202
|
elif event.key() == Qt.Key_Delete:
|
202
203
|
self.delete_annotation(self.layer.selected_annotation_index)
|
203
|
-
|
204
|
-
self.
|
204
|
+
elif event.key() == Qt.Key_H:
|
205
|
+
self.layer.toggle_annotation_visibility()
|
206
|
+
# self.parentWidget().keyPressEvent(event)
|
@@ -19,6 +19,7 @@ from pathlib import Path
|
|
19
19
|
|
20
20
|
class ImageListPanel(QDockWidget):
|
21
21
|
imageSelected = Signal(Path)
|
22
|
+
activeImageEntries = Signal(list)
|
22
23
|
|
23
24
|
def __init__(
|
24
25
|
self,
|
@@ -26,6 +27,7 @@ class ImageListPanel(QDockWidget):
|
|
26
27
|
processed_images: set[Path],
|
27
28
|
parent=None,
|
28
29
|
max_name_length=15,
|
30
|
+
images_per_page=10,
|
29
31
|
):
|
30
32
|
"""
|
31
33
|
:param image_entries: List of image paths to display.
|
@@ -35,7 +37,7 @@ class ImageListPanel(QDockWidget):
|
|
35
37
|
self.image_entries: list["ImageEntry"] = image_entries
|
36
38
|
self.processed_images = processed_images
|
37
39
|
self.current_page = 0
|
38
|
-
self.images_per_page =
|
40
|
+
self.images_per_page = images_per_page
|
39
41
|
self.max_name_length = max_name_length
|
40
42
|
self.init_ui()
|
41
43
|
|
@@ -90,7 +92,20 @@ class ImageListPanel(QDockWidget):
|
|
90
92
|
"""Update the image list with image paths and baked results."""
|
91
93
|
self.list_widget.clear()
|
92
94
|
|
93
|
-
for
|
95
|
+
# Calculate the range of images to display for the current page
|
96
|
+
start_index = self.current_page * self.images_per_page
|
97
|
+
end_index = min(start_index + self.images_per_page, len(image_entries))
|
98
|
+
|
99
|
+
# Update the pagination label
|
100
|
+
self.pagination_label.setText(
|
101
|
+
f"Showing {start_index + 1} to {end_index} of {len(image_entries)}"
|
102
|
+
)
|
103
|
+
|
104
|
+
# Display only the images for the current page
|
105
|
+
active_image_entries = []
|
106
|
+
for idx, image_entry in enumerate(
|
107
|
+
image_entries[start_index:end_index], start=start_index + 1
|
108
|
+
):
|
94
109
|
item_widget = QWidget()
|
95
110
|
item_layout = QHBoxLayout(item_widget)
|
96
111
|
item_layout.setContentsMargins(5, 5, 5, 5)
|
@@ -101,7 +116,7 @@ class ImageListPanel(QDockWidget):
|
|
101
116
|
thumbnail_pixmap = (
|
102
117
|
image_entry.data.get_thumbnail()
|
103
118
|
) # Baked result thumbnail
|
104
|
-
name_label_text = f"Baked Result {idx
|
119
|
+
name_label_text = f"Baked Result {idx}"
|
105
120
|
else:
|
106
121
|
thumbnail_pixmap = QPixmap(str(image_entry.data)).scaled(
|
107
122
|
50, 50, Qt.KeepAspectRatio, Qt.SmoothTransformation
|
@@ -126,11 +141,14 @@ class ImageListPanel(QDockWidget):
|
|
126
141
|
|
127
142
|
# Store metadata for the image
|
128
143
|
list_item.setData(Qt.UserRole, image_entry)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
)
|
144
|
+
active_image_entries.append(image_entry)
|
145
|
+
|
146
|
+
self.activeImageEntries.emit(active_image_entries)
|
147
|
+
# also set first item as selected if there are any items
|
148
|
+
if self.list_widget.count() > 0:
|
149
|
+
self.list_widget.setCurrentRow(0)
|
150
|
+
self.list_widget.setFocus()
|
151
|
+
|
134
152
|
self.update()
|
135
153
|
|
136
154
|
def handle_item_clicked(self, item: QListWidgetItem):
|
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import (
|
|
8
8
|
QSizePolicy,
|
9
9
|
QDockWidget,
|
10
10
|
QSlider,
|
11
|
+
QLineEdit,
|
11
12
|
)
|
12
13
|
|
13
14
|
from imagebaker.core.defs import LayerState
|
@@ -82,6 +83,32 @@ class LayerSettings(QDockWidget):
|
|
82
83
|
)
|
83
84
|
self.main_layout.addWidget(self.edge_width_slider["widget"])
|
84
85
|
|
86
|
+
# Caption input
|
87
|
+
caption_layout = QHBoxLayout()
|
88
|
+
caption_label = QLabel("Caption:")
|
89
|
+
caption_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
90
|
+
self.caption_input = QLineEdit()
|
91
|
+
self.caption_input.setPlaceholderText("Enter caption...")
|
92
|
+
self.caption_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
93
|
+
caption_layout.addWidget(caption_label)
|
94
|
+
caption_layout.addWidget(self.caption_input)
|
95
|
+
self.main_layout.addLayout(caption_layout)
|
96
|
+
|
97
|
+
# Set initial value if layer is selected
|
98
|
+
if self.selected_layer:
|
99
|
+
self.caption_input.setText(self.selected_layer.caption)
|
100
|
+
|
101
|
+
# Update layer caption on editing finished
|
102
|
+
def update_caption():
|
103
|
+
if self.selected_layer:
|
104
|
+
logger.info(
|
105
|
+
f"Updating caption for layer {self.selected_layer.layer_name} to {self.caption_input.text()}"
|
106
|
+
)
|
107
|
+
self.selected_layer.caption = self.caption_input.text()
|
108
|
+
self.selected_layer.update()
|
109
|
+
|
110
|
+
self.caption_input.editingFinished.connect(update_caption)
|
111
|
+
|
85
112
|
# Add stretch to push content to the top
|
86
113
|
self.main_layout.addStretch()
|
87
114
|
|
@@ -177,6 +204,7 @@ class LayerSettings(QDockWidget):
|
|
177
204
|
edge_opacity=self.selected_layer.edge_opacity,
|
178
205
|
edge_width=self.selected_layer.edge_width,
|
179
206
|
visible=self.selected_layer.visible,
|
207
|
+
caption=self.selected_layer.caption,
|
180
208
|
)
|
181
209
|
logger.info(f"Storing state {bake_settings}")
|
182
210
|
self.messageSignal.emit(f"Stored state for {bake_settings.layer_name}")
|
@@ -240,6 +268,7 @@ class LayerSettings(QDockWidget):
|
|
240
268
|
self.edge_width_slider["slider"].setValue(
|
241
269
|
int(self.selected_layer.edge_width)
|
242
270
|
)
|
271
|
+
self.caption_input.setText(self.selected_layer.caption)
|
243
272
|
else:
|
244
273
|
self.widget.setEnabled(False)
|
245
274
|
self.layer_name_label.setText("No BaseLayer")
|
imagebaker/tabs/baker_tab.py
CHANGED
@@ -18,7 +18,6 @@ from PySide6.QtWidgets import (
|
|
18
18
|
QSlider,
|
19
19
|
QLabel,
|
20
20
|
QSpinBox,
|
21
|
-
QComboBox,
|
22
21
|
)
|
23
22
|
from collections import deque
|
24
23
|
|
@@ -485,5 +484,54 @@ class BakerTab(QWidget):
|
|
485
484
|
self.layer_list.update_list()
|
486
485
|
self.messageSignal.emit(f"Added new layer: {new_layer.layer_name}")
|
487
486
|
|
487
|
+
if event.key() == Qt.Key_H:
|
488
|
+
# if pressed h key, then toggle visibility of layer
|
489
|
+
selected_layer = self.current_canvas._get_selected_layer()
|
490
|
+
if selected_layer:
|
491
|
+
selected_layer.visible = not selected_layer.visible
|
492
|
+
selected_layer.update()
|
493
|
+
self.current_canvas.update()
|
494
|
+
self.layer_settings.update_sliders()
|
495
|
+
self.messageSignal.emit(
|
496
|
+
f"Toggled visibility of annotations in layer: {selected_layer.layer_name}"
|
497
|
+
)
|
498
|
+
# if pressed W key, move the order of the selected layer up
|
499
|
+
# make it circular, if the first layer is selected, then move it to the last
|
500
|
+
if event.key() == Qt.Key_W:
|
501
|
+
selected_layer = self.current_canvas._get_selected_layer()
|
502
|
+
if selected_layer:
|
503
|
+
index = self.current_canvas.layers.index(selected_layer)
|
504
|
+
if index > 0:
|
505
|
+
# Move the layer up
|
506
|
+
self.current_canvas.layers.insert(index - 1, selected_layer)
|
507
|
+
del self.current_canvas.layers[index + 1]
|
508
|
+
else:
|
509
|
+
# Move the first layer to the end
|
510
|
+
self.current_canvas.layers.append(selected_layer)
|
511
|
+
del self.current_canvas.layers[0]
|
512
|
+
self.current_canvas.update()
|
513
|
+
self.layer_list.update_list()
|
514
|
+
self.messageSignal.emit(
|
515
|
+
f"Moved layer {selected_layer.layer_name} up in order."
|
516
|
+
)
|
517
|
+
# if pressed S key, move the order of the selected layer down
|
518
|
+
# make it circular, if the last layer is selected, then move it to the first
|
519
|
+
if event.key() == Qt.Key_S:
|
520
|
+
selected_layer = self.current_canvas._get_selected_layer()
|
521
|
+
if selected_layer:
|
522
|
+
index = self.current_canvas.layers.index(selected_layer)
|
523
|
+
if index < len(self.current_canvas.layers) - 1:
|
524
|
+
# Move the layer down
|
525
|
+
self.current_canvas.layers.insert(index + 2, selected_layer)
|
526
|
+
del self.current_canvas.layers[index]
|
527
|
+
else:
|
528
|
+
# Move the last layer to the beginning
|
529
|
+
self.current_canvas.layers.insert(0, selected_layer)
|
530
|
+
del self.current_canvas.layers[-1]
|
531
|
+
self.current_canvas.update()
|
532
|
+
self.layer_list.update_list()
|
533
|
+
self.messageSignal.emit(
|
534
|
+
f"Moved layer {selected_layer.layer_name} down in order."
|
535
|
+
)
|
488
536
|
self.update()
|
489
537
|
return super().keyPressEvent(event)
|