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.
- imagebaker/__init__.py +1 -1
- imagebaker/core/__init__.py +0 -0
- imagebaker/core/configs/__init__.py +1 -0
- imagebaker/core/configs/configs.py +156 -0
- imagebaker/core/defs/__init__.py +1 -0
- imagebaker/core/defs/defs.py +258 -0
- imagebaker/core/plugins/__init__.py +0 -0
- imagebaker/core/plugins/base_plugin.py +39 -0
- imagebaker/core/plugins/cosine_plugin.py +39 -0
- imagebaker/layers/__init__.py +3 -0
- imagebaker/layers/annotable_layer.py +847 -0
- imagebaker/layers/base_layer.py +724 -0
- imagebaker/layers/canvas_layer.py +1007 -0
- imagebaker/list_views/__init__.py +3 -0
- imagebaker/list_views/annotation_list.py +203 -0
- imagebaker/list_views/canvas_list.py +185 -0
- imagebaker/list_views/image_list.py +138 -0
- imagebaker/list_views/layer_list.py +390 -0
- imagebaker/list_views/layer_settings.py +219 -0
- imagebaker/models/__init__.py +0 -0
- imagebaker/models/base_model.py +150 -0
- imagebaker/tabs/__init__.py +2 -0
- imagebaker/tabs/baker_tab.py +496 -0
- imagebaker/tabs/layerify_tab.py +837 -0
- imagebaker/utils/__init__.py +0 -0
- imagebaker/utils/image.py +105 -0
- imagebaker/utils/state_utils.py +92 -0
- imagebaker/utils/transform_mask.py +107 -0
- imagebaker/window/__init__.py +1 -0
- imagebaker/window/app.py +136 -0
- imagebaker/window/main_window.py +181 -0
- imagebaker/workers/__init__.py +3 -0
- imagebaker/workers/baker_worker.py +247 -0
- imagebaker/workers/layerify_worker.py +91 -0
- imagebaker/workers/model_worker.py +54 -0
- {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/METADATA +6 -6
- imagebaker-0.0.48.dist-info/RECORD +41 -0
- {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/WHEEL +1 -1
- imagebaker-0.0.41.dist-info/RECORD +0 -7
- {imagebaker-0.0.41.dist-info/licenses → imagebaker-0.0.48.dist-info}/LICENSE +0 -0
- {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/entry_points.txt +0 -0
- {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,390 @@
|
|
1
|
+
# TODO: Use Signal and Slot for communication instead of passing objects around
|
2
|
+
from PySide6.QtCore import Qt, Signal
|
3
|
+
from PySide6.QtWidgets import (
|
4
|
+
QApplication,
|
5
|
+
QWidget,
|
6
|
+
QVBoxLayout,
|
7
|
+
QHBoxLayout,
|
8
|
+
QPushButton,
|
9
|
+
QLabel,
|
10
|
+
QMessageBox,
|
11
|
+
QDockWidget,
|
12
|
+
QListWidget,
|
13
|
+
QListWidgetItem,
|
14
|
+
QAbstractItemView,
|
15
|
+
QCheckBox,
|
16
|
+
QDialog,
|
17
|
+
)
|
18
|
+
|
19
|
+
# from imagebaker.tab_views import BaseLayer
|
20
|
+
from imagebaker.layers.canvas_layer import CanvasLayer as Canvas
|
21
|
+
from .layer_settings import LayerSettings
|
22
|
+
from imagebaker import logger
|
23
|
+
from imagebaker.layers.base_layer import BaseLayer
|
24
|
+
|
25
|
+
|
26
|
+
class LayerList(QDockWidget):
|
27
|
+
|
28
|
+
layersSelected = Signal(list)
|
29
|
+
messageSignal = Signal(str)
|
30
|
+
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
canvas: Canvas,
|
34
|
+
layer_settings: LayerSettings,
|
35
|
+
parent=None,
|
36
|
+
):
|
37
|
+
super().__init__("Layers", parent)
|
38
|
+
self.canvas = canvas
|
39
|
+
self.layers: list[BaseLayer] = []
|
40
|
+
self.layer_settings = layer_settings
|
41
|
+
self.init_ui()
|
42
|
+
|
43
|
+
def init_ui(self):
|
44
|
+
logger.info("Initializing LayerList")
|
45
|
+
main_widget = QWidget()
|
46
|
+
main_layout = QVBoxLayout(main_widget)
|
47
|
+
self.setMinimumWidth(150)
|
48
|
+
|
49
|
+
# Create list widget for layers
|
50
|
+
self.list_widget = QListWidget()
|
51
|
+
self.list_widget.setSelectionMode(QListWidget.MultiSelection)
|
52
|
+
|
53
|
+
# Enable drag and drop in the list widget
|
54
|
+
self.list_widget.setDragEnabled(True)
|
55
|
+
self.list_widget.setAcceptDrops(True)
|
56
|
+
self.list_widget.setDropIndicatorShown(True)
|
57
|
+
self.list_widget.setDragDropMode(QAbstractItemView.InternalMove)
|
58
|
+
|
59
|
+
# Connect signals
|
60
|
+
self.list_widget.itemClicked.connect(self.on_item_clicked)
|
61
|
+
self.list_widget.model().rowsMoved.connect(self.on_rows_moved)
|
62
|
+
self.list_widget.keyPressEvent = self.list_key_press_event
|
63
|
+
|
64
|
+
# Add list and buttons to main layout
|
65
|
+
main_layout.addWidget(self.list_widget)
|
66
|
+
# main_layout.addWidget(delete_button)
|
67
|
+
|
68
|
+
# Set main widget
|
69
|
+
self.setWidget(main_widget)
|
70
|
+
self.setFeatures(
|
71
|
+
QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable
|
72
|
+
)
|
73
|
+
|
74
|
+
def clear_layers(self):
|
75
|
+
"""Clear all layers from the list"""
|
76
|
+
self.layers.clear()
|
77
|
+
self.update_list()
|
78
|
+
|
79
|
+
def on_rows_moved(self, parent, start, end, destination, row):
|
80
|
+
"""Handle rows being moved in the list widget via drag and drop"""
|
81
|
+
# Calculate the source and destination indices
|
82
|
+
source_index = start
|
83
|
+
dest_index = row
|
84
|
+
|
85
|
+
# If moving down, we need to adjust the destination index
|
86
|
+
if dest_index > source_index:
|
87
|
+
dest_index -= 1
|
88
|
+
|
89
|
+
# Reorder the layers accordingly
|
90
|
+
if 0 <= source_index < len(self.layers) and 0 <= dest_index < len(self.layers):
|
91
|
+
# Move the layer in our internal list
|
92
|
+
layer = self.layers.pop(source_index)
|
93
|
+
self.layers.insert(dest_index, layer)
|
94
|
+
layer.order = dest_index
|
95
|
+
|
96
|
+
# Update the canvas with the new layer order
|
97
|
+
self.canvas.layers = self.layers
|
98
|
+
self.canvas.update()
|
99
|
+
|
100
|
+
# Update the UI
|
101
|
+
self.update_list()
|
102
|
+
logger.info(
|
103
|
+
f"BaseLayer: {layer.layer_name} moved from {source_index} to {dest_index}"
|
104
|
+
)
|
105
|
+
|
106
|
+
def update_list(self):
|
107
|
+
"""Update the list widget with current layers"""
|
108
|
+
# Remember current selection
|
109
|
+
selected_row = self.list_widget.currentRow()
|
110
|
+
|
111
|
+
# Clear the list
|
112
|
+
self.list_widget.clear()
|
113
|
+
selected_layer = None
|
114
|
+
|
115
|
+
if not self.canvas:
|
116
|
+
# No canvas, show dialog and return
|
117
|
+
logger.warning("No canvas found")
|
118
|
+
QDialog.critical(
|
119
|
+
self,
|
120
|
+
"Error",
|
121
|
+
"No canvas found. Please create a canvas first.",
|
122
|
+
QDialog.StandardButton.Ok,
|
123
|
+
)
|
124
|
+
|
125
|
+
# Add all layers to the list
|
126
|
+
for idx, layer in enumerate(self.layers):
|
127
|
+
if layer.selected:
|
128
|
+
selected_layer = layer
|
129
|
+
|
130
|
+
# Get annotation info
|
131
|
+
ann = layer.annotations[0]
|
132
|
+
thumbnail = layer.get_thumbnail(annotation=ann)
|
133
|
+
|
134
|
+
# Create widget for this layer item
|
135
|
+
widget = QWidget()
|
136
|
+
layout = QHBoxLayout(widget)
|
137
|
+
layout.setContentsMargins(5, 5, 5, 5)
|
138
|
+
|
139
|
+
# Add thumbnail
|
140
|
+
thumbnail_label = QLabel()
|
141
|
+
thumbnail_label.setPixmap(thumbnail)
|
142
|
+
layout.addWidget(thumbnail_label)
|
143
|
+
|
144
|
+
# Add text info
|
145
|
+
text_container = QWidget()
|
146
|
+
text_layout = QVBoxLayout(text_container)
|
147
|
+
|
148
|
+
# Main label
|
149
|
+
main_label = QLabel(ann.label)
|
150
|
+
text_color = "#666" if not layer.visible else "black"
|
151
|
+
if layer.selected:
|
152
|
+
text_color = "blue"
|
153
|
+
main_label.setStyleSheet(f"font-weight: bold; color: {text_color};")
|
154
|
+
text_layout.addWidget(main_label)
|
155
|
+
|
156
|
+
# Add secondary info if available
|
157
|
+
secondary_text = []
|
158
|
+
score_text = (
|
159
|
+
f"{ann.annotator}: {ann.score:.2f}"
|
160
|
+
if ann.score is not None
|
161
|
+
else ann.annotator
|
162
|
+
)
|
163
|
+
secondary_text.append(score_text)
|
164
|
+
short_path = ann.file_path.stem
|
165
|
+
secondary_text.append(f"<span style='color:#666;'>{short_path}</span>")
|
166
|
+
|
167
|
+
if secondary_text:
|
168
|
+
info_color = "#888" if not layer.visible else "#444"
|
169
|
+
info_label = QLabel("<br>".join(secondary_text))
|
170
|
+
info_label.setStyleSheet(f"color: {info_color}; font-size: 10px;")
|
171
|
+
info_label.setTextFormat(Qt.RichText)
|
172
|
+
text_layout.addWidget(info_label)
|
173
|
+
|
174
|
+
text_layout.addStretch()
|
175
|
+
layout.addWidget(text_container)
|
176
|
+
layout.addStretch()
|
177
|
+
|
178
|
+
# Add control buttons
|
179
|
+
btn_container = QWidget()
|
180
|
+
btn_layout = QHBoxLayout(btn_container)
|
181
|
+
|
182
|
+
# Visibility button
|
183
|
+
vis_btn = QPushButton("👀" if layer.visible else "👁️")
|
184
|
+
vis_btn.setMaximumWidth(self.canvas.config.normal_draw_config.button_width)
|
185
|
+
vis_btn.setCheckable(True)
|
186
|
+
vis_btn.setChecked(layer.visible)
|
187
|
+
vis_btn.setToolTip("Visible" if layer.visible else "Hidden")
|
188
|
+
|
189
|
+
# Store layer index for button callbacks
|
190
|
+
vis_btn.setProperty("layer_index", idx)
|
191
|
+
vis_btn.clicked.connect(
|
192
|
+
lambda checked, btn=vis_btn: self.toggle_visibility(
|
193
|
+
btn.property("layer_index")
|
194
|
+
)
|
195
|
+
)
|
196
|
+
btn_layout.addWidget(vis_btn)
|
197
|
+
|
198
|
+
# Delete button
|
199
|
+
del_btn = QPushButton("🗑️")
|
200
|
+
del_btn.setMaximumWidth(self.canvas.config.normal_draw_config.button_width)
|
201
|
+
del_btn.setToolTip("Delete annotation")
|
202
|
+
del_btn.setProperty("layer_index", idx)
|
203
|
+
del_btn.clicked.connect(
|
204
|
+
lambda checked=False, idx=idx: self.confirm_delete_layer(idx)
|
205
|
+
)
|
206
|
+
btn_layout.addWidget(del_btn)
|
207
|
+
|
208
|
+
# a checkbox to toggle layer annotation export
|
209
|
+
export_checkbox = QCheckBox()
|
210
|
+
export_checkbox.setChecked(layer.allow_annotation_export)
|
211
|
+
export_checkbox.setToolTip("Toggle annotation export")
|
212
|
+
export_checkbox.setProperty("layer_index", idx)
|
213
|
+
export_checkbox.stateChanged.connect(
|
214
|
+
lambda state, idx=idx: self.toggle_annotation_export(idx, state)
|
215
|
+
)
|
216
|
+
btn_layout.addWidget(export_checkbox)
|
217
|
+
|
218
|
+
layout.addWidget(btn_container)
|
219
|
+
|
220
|
+
# Add item to list widget
|
221
|
+
item = QListWidgetItem()
|
222
|
+
item.setSizeHint(widget.sizeHint())
|
223
|
+
self.list_widget.addItem(item)
|
224
|
+
self.list_widget.setItemWidget(item, widget)
|
225
|
+
|
226
|
+
# Restore selection if possible
|
227
|
+
if selected_row >= 0 and selected_row < self.list_widget.count():
|
228
|
+
self.list_widget.setCurrentRow(selected_row)
|
229
|
+
|
230
|
+
# Update layer settings panel
|
231
|
+
if selected_layer:
|
232
|
+
self.layer_settings.set_selected_layer(selected_layer)
|
233
|
+
else:
|
234
|
+
self.layer_settings.set_selected_layer(None)
|
235
|
+
self.update()
|
236
|
+
|
237
|
+
def toggle_annotation_export(self, index, state):
|
238
|
+
"""Toggle annotation export for a layer by index"""
|
239
|
+
if 0 <= index < len(self.layers):
|
240
|
+
layer = self.layers[index]
|
241
|
+
layer.allow_annotation_export = not layer.allow_annotation_export
|
242
|
+
logger.info(
|
243
|
+
f"BaseLayer annotation export toggled: {layer.layer_name}, {layer.allow_annotation_export}"
|
244
|
+
)
|
245
|
+
self.update_list()
|
246
|
+
layer.update()
|
247
|
+
self.canvas.update()
|
248
|
+
|
249
|
+
def on_item_clicked(self, item):
|
250
|
+
"""Handle layer selection with:
|
251
|
+
- Left click only: Toggle clicked layer and deselect others
|
252
|
+
- Ctrl+Left click: Toggle clicked layer only (keep others selected)"""
|
253
|
+
modifiers = QApplication.keyboardModifiers()
|
254
|
+
current_row = self.list_widget.row(item)
|
255
|
+
|
256
|
+
if 0 <= current_row < len(self.layers):
|
257
|
+
current_layer = self.layers[current_row]
|
258
|
+
|
259
|
+
if modifiers & Qt.ControlModifier:
|
260
|
+
# Ctrl+Click: Toggle just this layer's selection
|
261
|
+
current_layer.selected = not current_layer.selected
|
262
|
+
selected_layers = [layer for layer in self.layers if layer.selected]
|
263
|
+
else:
|
264
|
+
# Normal click: Toggle this layer and deselect all others
|
265
|
+
was_selected = current_layer.selected
|
266
|
+
for layer in self.layers:
|
267
|
+
layer.selected = False
|
268
|
+
current_layer.selected = not was_selected # Toggle
|
269
|
+
selected_layers = [current_layer] if current_layer.selected else []
|
270
|
+
|
271
|
+
# Update UI
|
272
|
+
self.layersSelected.emit(selected_layers)
|
273
|
+
self.layer_settings.set_selected_layer(
|
274
|
+
selected_layers[0] if selected_layers else None
|
275
|
+
)
|
276
|
+
self.canvas.update()
|
277
|
+
self.update_list()
|
278
|
+
|
279
|
+
logger.info(f"Selected layers: {[l.layer_name for l in selected_layers]}")
|
280
|
+
|
281
|
+
def on_layer_selected(self, indices):
|
282
|
+
"""Select multiple layers by indices"""
|
283
|
+
selected_layers = []
|
284
|
+
for i, layer in enumerate(self.layers):
|
285
|
+
if i in indices:
|
286
|
+
layer.selected = True
|
287
|
+
selected_layers.append(layer)
|
288
|
+
else:
|
289
|
+
layer.selected = False
|
290
|
+
|
291
|
+
# Emit the selected layers
|
292
|
+
self.layersSelected.emit(selected_layers)
|
293
|
+
|
294
|
+
# Update UI
|
295
|
+
self.layer_settings.set_selected_layer(
|
296
|
+
selected_layers[0] if selected_layers else None
|
297
|
+
)
|
298
|
+
self.canvas.update()
|
299
|
+
self.update_list()
|
300
|
+
|
301
|
+
def confirm_delete_layer(self, index):
|
302
|
+
"""Show confirmation dialog before deleting a layer"""
|
303
|
+
if 0 <= index < len(self.layers):
|
304
|
+
msg_box = QMessageBox()
|
305
|
+
msg_box.setIcon(QMessageBox.Question)
|
306
|
+
msg_box.setWindowTitle("Confirm Deletion")
|
307
|
+
msg_box.setText("Are you sure you want to delete this layer?")
|
308
|
+
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
309
|
+
msg_box.setDefaultButton(QMessageBox.No)
|
310
|
+
|
311
|
+
result = msg_box.exec_()
|
312
|
+
if result == QMessageBox.Yes:
|
313
|
+
self.delete_layer(index)
|
314
|
+
|
315
|
+
def list_key_press_event(self, event):
|
316
|
+
"""Handle key press events in the list widget"""
|
317
|
+
if event.key() == Qt.Key_Delete:
|
318
|
+
current_row = self.list_widget.currentRow()
|
319
|
+
if current_row >= 0:
|
320
|
+
self.confirm_delete_layer(current_row)
|
321
|
+
else:
|
322
|
+
# Pass other key events to the parent class
|
323
|
+
QListWidget.keyPressEvent(self.list_widget, event)
|
324
|
+
|
325
|
+
def delete_selected_layer(self):
|
326
|
+
"""Delete the currently selected layer with confirmation"""
|
327
|
+
current_row = self.list_widget.currentRow()
|
328
|
+
if current_row >= 0:
|
329
|
+
self.confirm_delete_layer(current_row)
|
330
|
+
|
331
|
+
def delete_layer(self, index):
|
332
|
+
"""Delete a layer by index"""
|
333
|
+
if 0 <= index < len(self.layers):
|
334
|
+
logger.info(f"Deleting layer: {self.layers[index].layer_name}")
|
335
|
+
del self.layers[index]
|
336
|
+
self.update_list()
|
337
|
+
self.canvas.layers = self.layers
|
338
|
+
self.canvas.update()
|
339
|
+
logger.info(f"BaseLayer deleted: {index}")
|
340
|
+
|
341
|
+
def toggle_visibility(self, index):
|
342
|
+
"""Toggle visibility of a layer by index"""
|
343
|
+
if 0 <= index < len(self.layers):
|
344
|
+
layer = self.layers[index]
|
345
|
+
layer.visible = not layer.visible
|
346
|
+
logger.info(
|
347
|
+
f"BaseLayer visibility toggled: {layer.layer_name}, {layer.visible}"
|
348
|
+
)
|
349
|
+
self.update_list()
|
350
|
+
self.canvas.update()
|
351
|
+
|
352
|
+
def add_layer(self, layer: BaseLayer = None):
|
353
|
+
"""Add a new layer to the list"""
|
354
|
+
if layer is None:
|
355
|
+
return
|
356
|
+
self.layers.append(layer)
|
357
|
+
self.update_list()
|
358
|
+
self.canvas.layers = self.layers
|
359
|
+
# self.canvas.update()
|
360
|
+
|
361
|
+
def select_layer(self, layer):
|
362
|
+
"""Select a specific layer"""
|
363
|
+
logger.info(f"Selecting layer: {layer.layer_name}")
|
364
|
+
self.update_list()
|
365
|
+
|
366
|
+
def get_selected_layers(self):
|
367
|
+
"""Returns list of currently selected BaseLayer objects"""
|
368
|
+
selected_items = self.list_widget.selectedItems()
|
369
|
+
return [
|
370
|
+
self.layers[self.list_widget.row(item)]
|
371
|
+
for item in selected_items
|
372
|
+
if 0 <= self.list_widget.row(item) < len(self.layers)
|
373
|
+
]
|
374
|
+
|
375
|
+
def keyPressEvent(self, event):
|
376
|
+
"""Handle key presses."""
|
377
|
+
self.selected_layer = self.canvas.selected_layer
|
378
|
+
if self.selected_layer is None:
|
379
|
+
return
|
380
|
+
if event.key() == Qt.Key_Delete:
|
381
|
+
self.canvas.delete_layer()
|
382
|
+
elif event.key() == Qt.Key_Escape:
|
383
|
+
self.canvas.selected_layer = None
|
384
|
+
elif event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_C:
|
385
|
+
self.canvas.copy_layer()
|
386
|
+
elif event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_V:
|
387
|
+
self.canvas.paste_layer()
|
388
|
+
elif event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_Z:
|
389
|
+
# self.selected_layer.undo()
|
390
|
+
self.messageSignal.emit("Undo not implemented yet")
|
@@ -0,0 +1,219 @@
|
|
1
|
+
from PySide6.QtCore import Qt, Signal
|
2
|
+
from PySide6.QtWidgets import (
|
3
|
+
QWidget,
|
4
|
+
QVBoxLayout,
|
5
|
+
QHBoxLayout,
|
6
|
+
QPushButton,
|
7
|
+
QLabel,
|
8
|
+
QSizePolicy,
|
9
|
+
QDockWidget,
|
10
|
+
QSlider,
|
11
|
+
)
|
12
|
+
|
13
|
+
from imagebaker.core.defs import LayerState
|
14
|
+
|
15
|
+
from imagebaker.layers.base_layer import BaseLayer
|
16
|
+
from imagebaker import logger
|
17
|
+
|
18
|
+
|
19
|
+
class LayerSettings(QDockWidget):
|
20
|
+
layerState = Signal(LayerState)
|
21
|
+
messageSignal = Signal(str)
|
22
|
+
|
23
|
+
def __init__(self, parent=None, max_xpos=1000, max_ypos=1000, max_scale=100):
|
24
|
+
super().__init__("BaseLayer Settings", parent)
|
25
|
+
self.selected_layer: BaseLayer = None
|
26
|
+
|
27
|
+
self._disable_updates = False
|
28
|
+
self.last_updated_time = 0
|
29
|
+
self.max_xpos = max_xpos
|
30
|
+
self.max_ypos = max_ypos
|
31
|
+
self.max_scale = max_scale
|
32
|
+
self.init_ui()
|
33
|
+
self.setFeatures(
|
34
|
+
QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable
|
35
|
+
)
|
36
|
+
self.update_sliders()
|
37
|
+
|
38
|
+
def init_ui(self):
|
39
|
+
"""Initialize the UI elements."""
|
40
|
+
logger.info("Initializing LayerSettings")
|
41
|
+
self.widget = QWidget()
|
42
|
+
self.setWidget(self.widget)
|
43
|
+
self.main_layout = QVBoxLayout(self.widget)
|
44
|
+
self.main_layout.setContentsMargins(10, 10, 10, 10) # Add some padding
|
45
|
+
self.main_layout.setSpacing(10)
|
46
|
+
|
47
|
+
# BaseLayer layer_name label
|
48
|
+
self.layer_name_label = QLabel("No BaseLayer Selected")
|
49
|
+
self.layer_name_label.setAlignment(Qt.AlignCenter)
|
50
|
+
self.layer_name_label.setStyleSheet("font-weight: bold; font-size: 14px;")
|
51
|
+
self.main_layout.addWidget(self.layer_name_label)
|
52
|
+
|
53
|
+
# Opacity slider
|
54
|
+
self.opacity_slider = self.create_slider("Opacity:", 0, 255, 255, 1)
|
55
|
+
self.main_layout.addWidget(self.opacity_slider["widget"])
|
56
|
+
self.x_slider = self.create_slider("X:", -self.max_xpos, self.max_xpos, 0, 1)
|
57
|
+
self.main_layout.addWidget(self.x_slider["widget"])
|
58
|
+
self.y_slider = self.create_slider("Y:", -self.max_ypos, self.max_ypos, 0, 1)
|
59
|
+
self.main_layout.addWidget(self.y_slider["widget"])
|
60
|
+
self.scale_x_slider = self.create_slider(
|
61
|
+
"Scale X:", -self.max_scale, self.max_scale, 100, 100
|
62
|
+
) # 1-500 becomes 0.01-5.0
|
63
|
+
self.main_layout.addWidget(self.scale_x_slider["widget"])
|
64
|
+
self.scale_y_slider = self.create_slider(
|
65
|
+
"Scale Y:", -self.max_scale, self.max_scale, 100, 100
|
66
|
+
)
|
67
|
+
self.main_layout.addWidget(self.scale_y_slider["widget"])
|
68
|
+
self.rotation_slider = self.create_slider("Rotation:", 0, 360, 0, 1)
|
69
|
+
self.main_layout.addWidget(self.rotation_slider["widget"])
|
70
|
+
|
71
|
+
# Add stretch to push content to the top
|
72
|
+
self.main_layout.addStretch()
|
73
|
+
|
74
|
+
# Ensure the dock widget resizes properly
|
75
|
+
self.setMinimumWidth(250) # Minimum width for usability
|
76
|
+
self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
77
|
+
|
78
|
+
def create_slider(self, label, min_val, max_val, default, scale_factor=1):
|
79
|
+
"""Create a slider with a label and value display."""
|
80
|
+
container = QWidget()
|
81
|
+
layout = QHBoxLayout(container)
|
82
|
+
layout.setContentsMargins(0, 0, 0, 0) # Remove inner margins
|
83
|
+
|
84
|
+
# Label
|
85
|
+
lbl = QLabel(label)
|
86
|
+
lbl.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
87
|
+
|
88
|
+
# Slider
|
89
|
+
slider = QSlider(Qt.Horizontal)
|
90
|
+
slider.setRange(min_val, max_val)
|
91
|
+
slider.setValue(default)
|
92
|
+
slider.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
93
|
+
|
94
|
+
# Value label
|
95
|
+
value_lbl = QLabel(f"{default / scale_factor:.1f}")
|
96
|
+
value_lbl.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
97
|
+
|
98
|
+
# Connect slider to value label (still updates during drag)
|
99
|
+
slider.valueChanged.connect(
|
100
|
+
lambda v: value_lbl.setText(f"{v / scale_factor:.1f}")
|
101
|
+
)
|
102
|
+
|
103
|
+
# Only update layer on slider release
|
104
|
+
slider.sliderReleased.connect(self.on_slider_released)
|
105
|
+
|
106
|
+
# Add widgets to layout
|
107
|
+
layout.addWidget(lbl)
|
108
|
+
layout.addWidget(slider)
|
109
|
+
layout.addWidget(value_lbl)
|
110
|
+
|
111
|
+
return {
|
112
|
+
"widget": container,
|
113
|
+
"slider": slider,
|
114
|
+
"label": value_lbl,
|
115
|
+
"scale_factor": scale_factor,
|
116
|
+
}
|
117
|
+
|
118
|
+
def on_slider_released(self):
|
119
|
+
"""Update layer only when slider is released"""
|
120
|
+
if self._disable_updates or not self.selected_layer:
|
121
|
+
return
|
122
|
+
|
123
|
+
sender = self.sender() # Get which slider was released
|
124
|
+
value = sender.value()
|
125
|
+
|
126
|
+
try:
|
127
|
+
self._disable_updates = True
|
128
|
+
|
129
|
+
if sender == self.opacity_slider["slider"]:
|
130
|
+
self.selected_layer.opacity = value
|
131
|
+
elif sender == self.x_slider["slider"]:
|
132
|
+
self.selected_layer.position.setX(value / self.x_slider["scale_factor"])
|
133
|
+
elif sender == self.y_slider["slider"]:
|
134
|
+
self.selected_layer.position.setY(value / self.y_slider["scale_factor"])
|
135
|
+
elif sender == self.scale_x_slider["slider"]:
|
136
|
+
self.selected_layer.scale_x = value / 100.0
|
137
|
+
elif sender == self.scale_y_slider["slider"]:
|
138
|
+
self.selected_layer.scale_y = value / 100.0
|
139
|
+
elif sender == self.rotation_slider["slider"]:
|
140
|
+
self.selected_layer.rotation = value
|
141
|
+
|
142
|
+
self.selected_layer.update() # Trigger a repaint
|
143
|
+
|
144
|
+
finally:
|
145
|
+
self._disable_updates = False
|
146
|
+
|
147
|
+
def emit_bake_settings(self):
|
148
|
+
"""Emit the bake settings signal."""
|
149
|
+
bake_settings = LayerState(
|
150
|
+
layer_id=self.selected_layer.id,
|
151
|
+
order=self.selected_layer.order,
|
152
|
+
layer_name=self.selected_layer.layer_name,
|
153
|
+
position=self.selected_layer.position,
|
154
|
+
rotation=self.selected_layer.rotation,
|
155
|
+
scale_x=self.selected_layer.scale_x,
|
156
|
+
scale_y=self.selected_layer.scale_y,
|
157
|
+
)
|
158
|
+
logger.info(f"Storing state {bake_settings}")
|
159
|
+
self.messageSignal.emit(f"Stored state for {bake_settings.layer_name}")
|
160
|
+
self.layerState.emit(bake_settings)
|
161
|
+
|
162
|
+
def set_selected_layer(self, layer):
|
163
|
+
"""Set the currently selected layer."""
|
164
|
+
self.selected_layer = layer
|
165
|
+
self.update_sliders()
|
166
|
+
|
167
|
+
def update_sliders(self):
|
168
|
+
"""Update slider values based on the selected layer."""
|
169
|
+
self.widget.setEnabled(False)
|
170
|
+
if self._disable_updates or not self.selected_layer:
|
171
|
+
return
|
172
|
+
|
173
|
+
try:
|
174
|
+
self._disable_updates = True
|
175
|
+
|
176
|
+
if self.selected_layer.selected:
|
177
|
+
self.widget.setEnabled(True)
|
178
|
+
self.layer_name_label.setText(
|
179
|
+
f"BaseLayer: {self.selected_layer.layer_name}"
|
180
|
+
)
|
181
|
+
new_max_xpos = self.selected_layer.config.max_xpos
|
182
|
+
new_max_ypos = self.selected_layer.config.max_ypos
|
183
|
+
|
184
|
+
if new_max_xpos - abs(self.selected_layer.position.x()) < 50:
|
185
|
+
new_max_xpos = abs(self.selected_layer.position.x()) + 50
|
186
|
+
if new_max_ypos - abs(self.selected_layer.position.y()) < 50:
|
187
|
+
new_max_ypos = abs(self.selected_layer.position.y()) + 50
|
188
|
+
|
189
|
+
# Update slider ranges
|
190
|
+
self.x_slider["slider"].setRange(
|
191
|
+
-new_max_xpos,
|
192
|
+
new_max_xpos,
|
193
|
+
)
|
194
|
+
self.y_slider["slider"].setRange(
|
195
|
+
-new_max_ypos,
|
196
|
+
new_max_ypos,
|
197
|
+
)
|
198
|
+
|
199
|
+
# Update slider values
|
200
|
+
self.opacity_slider["slider"].setValue(
|
201
|
+
int(self.selected_layer.opacity) # Scale back to 0-255
|
202
|
+
)
|
203
|
+
self.x_slider["slider"].setValue(int(self.selected_layer.position.x()))
|
204
|
+
self.y_slider["slider"].setValue(int(self.selected_layer.position.y()))
|
205
|
+
self.scale_x_slider["slider"].setValue(
|
206
|
+
int(self.selected_layer.scale_x * 100)
|
207
|
+
)
|
208
|
+
self.scale_y_slider["slider"].setValue(
|
209
|
+
int(self.selected_layer.scale_y * 100)
|
210
|
+
)
|
211
|
+
self.rotation_slider["slider"].setValue(
|
212
|
+
int(self.selected_layer.rotation)
|
213
|
+
)
|
214
|
+
else:
|
215
|
+
self.widget.setEnabled(False)
|
216
|
+
self.layer_name_label.setText("No BaseLayer")
|
217
|
+
finally:
|
218
|
+
self._disable_updates = False
|
219
|
+
self.update()
|
File without changes
|