batoolset-gui 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. batoolset/GUI/__init__.py +0 -0
  2. batoolset/GUI/customdialog.py +136 -0
  3. batoolset/GUI/exception_handler.py +74 -0
  4. batoolset/GUI/list_gui.py +514 -0
  5. batoolset/GUI/multi_image_display.py +1038 -0
  6. batoolset/GUI/open.py +325 -0
  7. batoolset/GUI/overlay_hints.py +45 -0
  8. batoolset/GUI/paint2.py +1369 -0
  9. batoolset/GUI/scrollablepaint.py +1165 -0
  10. batoolset/dialogs/__init__.py +0 -0
  11. batoolset/dialogs/opensave.py +228 -0
  12. batoolset/drawings/__init__.py +0 -0
  13. batoolset/drawings/math_text.py +294 -0
  14. batoolset/drawings/pdf_svg_renderer.py +376 -0
  15. batoolset/drawings/shapes/Position.py +183 -0
  16. batoolset/drawings/shapes/__init__.py +0 -0
  17. batoolset/drawings/shapes/automated_alignment_drawing_refactored.py +346 -0
  18. batoolset/drawings/shapes/circle2d.py +99 -0
  19. batoolset/drawings/shapes/compute_optimal_size_all_merged.py +328 -0
  20. batoolset/drawings/shapes/ellipse2d.py +336 -0
  21. batoolset/drawings/shapes/freehand2d.py +235 -0
  22. batoolset/drawings/shapes/group.py +1985 -0
  23. batoolset/drawings/shapes/image2d.py +3157 -0
  24. batoolset/drawings/shapes/line2d.py +579 -0
  25. batoolset/drawings/shapes/point2d.py +257 -0
  26. batoolset/drawings/shapes/polygon2d.py +357 -0
  27. batoolset/drawings/shapes/polyline2d.py +235 -0
  28. batoolset/drawings/shapes/rect2d.py +388 -0
  29. batoolset/drawings/shapes/rectangle2d.py +601 -0
  30. batoolset/drawings/shapes/scalebar.py +310 -0
  31. batoolset/drawings/shapes/serializablefont.py +133 -0
  32. batoolset/drawings/shapes/serializableguiparams.py +14 -0
  33. batoolset/drawings/shapes/square2d.py +102 -0
  34. batoolset/drawings/shapes/test_publication_style.py +220 -0
  35. batoolset/drawings/shapes/textorientation.py +45 -0
  36. batoolset/drawings/shapes/tools.py +467 -0
  37. batoolset/drawings/shapes/transform2D.py +350 -0
  38. batoolset/drawings/shapes/txt2d.py +615 -0
  39. batoolset/drawings/svg_clip_renderer.py +247 -0
  40. batoolset/drawings/svg_embed.py +165 -0
  41. batoolset/drawings/svg_text_renderer.py +183 -0
  42. batoolset/drawings/widgets/__init__.py +0 -0
  43. batoolset/drawings/widgets/col.py +343 -0
  44. batoolset/drawings/widgets/image_group.py +248 -0
  45. batoolset/drawings/widgets/image_organizer.py +96 -0
  46. batoolset/drawings/widgets/paint.py +184 -0
  47. batoolset/drawings/widgets/vectorial.py +256 -0
  48. batoolset/figure/__init__.py +0 -0
  49. batoolset/figure/alignment.py +822 -0
  50. batoolset/figure/column.py +1117 -0
  51. batoolset/figure/fig_tools.py +285 -0
  52. batoolset/figure/minimal_debugger_scale_etc.py +139 -0
  53. batoolset/figure/panel.py +697 -0
  54. batoolset/figure/row.py +852 -0
  55. batoolset/figure/user_selection.py +287 -0
  56. batoolset/icons/__init__.py +0 -0
  57. batoolset/icons/tools.py +248 -0
  58. batoolset/luts/__init__.py +0 -0
  59. batoolset/luts/lut_minimal_test.py +1308 -0
  60. batoolset/luts/lutcreatorfromformula.py +209 -0
  61. batoolset/luts/luteditor.py +243 -0
  62. batoolset/pyqts/__init__.py +0 -0
  63. batoolset/pyqts/pleasewaitdialog.py +14 -0
  64. batoolset/pyqts/tools.py +760 -0
  65. batoolset/serializations/__init__.py +0 -0
  66. batoolset/serializations/tools.py +1338 -0
  67. batoolset/theme.py +647 -0
  68. batoolset/uis/__init__.py +0 -0
  69. batoolset/uis/blinker.py +56 -0
  70. batoolset_gui-0.2.0.dist-info/METADATA +54 -0
  71. batoolset_gui-0.2.0.dist-info/RECORD +74 -0
  72. batoolset_gui-0.2.0.dist-info/WHEEL +5 -0
  73. batoolset_gui-0.2.0.dist-info/licenses/LICENSE +24 -0
  74. batoolset_gui-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,328 @@
1
+ from batoolset.drawings.shapes.rectangle2d import Rectangle2D
2
+ # from PyQt5.QtCore import QRectF, QSizeF
3
+ from qtpy.QtCore import QRectF, QSizeF
4
+
5
+ # def resize_rect_preserve_aspect_ratio(rect: Rectangle2D, new_height: float):
6
+ # current_width = rect.width()
7
+ # current_height = rect.height()
8
+ # aspect_ratio = current_width / current_height
9
+ #
10
+ # new_width = aspect_ratio * new_height
11
+ # # new_rect = Rectangle2D(QRectF(rect.x(), rect.y(), new_width, new_height)) # here I rather need to scale the rect
12
+ # # rect.set_to_scale(aspect_ratio)
13
+ # # dirty hack for test
14
+ # # rect = Rectangle2D(rect)
15
+ #
16
+ # # print(current_width, new_width)
17
+ #
18
+ # rect.set_to_scale(current_width/new_width)
19
+ #
20
+ # return rect
21
+
22
+
23
+
24
+
25
+ # TODO --> fuse all of this in a single super smart stuff
26
+
27
+
28
+
29
+
30
+ # can I use my own LLM there ???
31
+
32
+
33
+ def resize_rect_preserve_aspect_ratio(rect: Rectangle2D, new_width: float, new_height:float):
34
+ # I guess the bug is there
35
+ current_width = rect.width()
36
+ current_height = rect.height()
37
+
38
+
39
+ # if current_height == 0:
40
+ # return rect
41
+ # print(current_height)
42
+ aspect_ratio = float(current_width) / float(current_height)
43
+
44
+ # very good --> so I could fix it
45
+ if new_height is not None:
46
+ # if False: # this is to favor height
47
+ # new_height = new_size.height()
48
+ # # new_width = aspect_ratio * new_height if aspect_ratio < new_size.width() / new_size.height() else new_size.width() * current_height / current_width
49
+ new_width = aspect_ratio * new_height
50
+ rect.set_to_scale(1./(new_width/current_width))
51
+ else:
52
+ # new_width = new_size.width()
53
+ new_height = new_width/aspect_ratio
54
+ rect.set_to_scale(1./(new_height / current_height)) # TODO --> clean this code --> make it better
55
+
56
+ # new_rect = QRectF(rect.x(), rect.y(), new_width, new_height)
57
+ # return new_rect
58
+ return rect
59
+
60
+
61
+ def resize_rects(rects: list, target_width:float, target_height: float):
62
+ resized_rects = []
63
+ for rect in rects:
64
+ resized_rect = resize_rect_preserve_aspect_ratio(rect, target_width, target_height)
65
+ resized_rects.append(resized_rect)
66
+
67
+ return resized_rects
68
+
69
+ # I need get its size set its scale to 1 get just the text size yet crop the
70
+ def resize_incompressible_rects(rects: list, target_width:float, target_height: float):
71
+ # max_w = 0
72
+ # max_h = 0
73
+ # failed_images = []
74
+ # text_images = []
75
+ # individual_width = []
76
+ # individual_height = []
77
+ # for elm in self.paint.EZFIG_panel.selected_shape:
78
+ # try:
79
+ # print(elm, elm.annotations, elm.annotations[0], elm.annotations[0].width(all=True),
80
+ # elm.annotations[0].height(all=True))
81
+ # if len(elm.annotations) != 1:
82
+ # raise Exception('not good')
83
+ # individual_width.append(elm.annotations[0].width(all=True))
84
+ # individual_height.append(elm.annotations[0].height(all=True))
85
+ # max_w = max(elm.annotations[0].width(all=True), max_w)
86
+ # max_h = max(elm.annotations[0].height(all=True), max_h)
87
+ # text_images.append(elm)
88
+ # except:
89
+ # # pass # if fails this is not a text image
90
+ # failed_images.append(elm) # if image is failed then we have it
91
+
92
+
93
+
94
+ max_w = 0
95
+ max_h = 0
96
+ resized_rects = []
97
+ for rect in rects:
98
+ # resized_rect = resize_rect_preserve_aspect_ratio(rect, target_width, target_height)
99
+ # resized_rects.append(resized_rect)
100
+ if not getattr(rect, 'annotations', None):
101
+ # a label whose text was removed: an IndexError here broke the
102
+ # layout of its whole row (and the drag preview)
103
+ continue
104
+ txt_width = rect.annotations[0].width(all=True)
105
+ txt_height = rect.annotations[0].height(all=True)
106
+ max_w = max(txt_width, max_w)
107
+ max_h = max(txt_height, max_h)
108
+ # set the texts to their minimal size --> then resize the stuff in the very end so that all fits
109
+ rect.setWidth(txt_width)
110
+ rect.setHeight(txt_height)
111
+
112
+ return max_w, max_h, resized_rects
113
+
114
+ # def calculate_target_width(rects: list, max_total_height: float):
115
+ # total_aspect_ratio = sum(rect.height() / rect.width() for rect in rects if not rect.immutable) # immutable objects should be removed from the stuff -−> TODO
116
+ # target_width = max_total_height / total_aspect_ratio
117
+ # return target_width
118
+
119
+ # def calculate_target_height(rects: list, max_total_width: float):
120
+ # # in fact if there are incompressible ones here I need
121
+ # total_aspect_ratio = sum(rect.width() / rect.height() for rect in rects if not rect.immutable) # immutable objects should be removed from the stuff -−> TODO
122
+ # target_height = max_total_width / total_aspect_ratio
123
+ # return target_height
124
+
125
+
126
+ # def resize_rects_within_total_width(rects: list, max_total_width: float, space_in_px: float):
127
+ # incompressible_width = (len(rects) - 1) * space_in_px
128
+ # available_width = max_total_width - incompressible_width
129
+ #
130
+ # target_height = calculate_target_height(rects, available_width)
131
+ # resized_rects = resize_rects(rects, None, target_height) # --> indeed that works
132
+ #
133
+ # return resized_rects
134
+
135
+ # def resize_rects_within_total_height(rects: list, max_total_height: float, space_in_px: float):
136
+ # incompressible_height = (len(rects) - 1) * space_in_px
137
+ # available_height = max_total_height - incompressible_height
138
+ #
139
+ # target_width = calculate_target_width(rects, available_height)
140
+ # resized_rects = resize_rects(rects, target_width,None) # --> indeed that works
141
+ #
142
+ # return resized_rects
143
+
144
+ def _empty_group(rect):
145
+ """A group with nothing in it yet (a row added to be filled later): it has no
146
+ size, so its aspect ratio divided by zero and the whole row was not laid out
147
+ (PyFigures chat '3 rows per 2 cols', 2026-11-03). Sized when filled."""
148
+ return isinstance(getattr(rect, 'content', None), list) and not rect.content
149
+
150
+
151
+ def resize_rects_within_total_width(rects: list, max_total_width: float, sum_incompressible_space_along_x: float):
152
+ # incompressible_width = (len(rects) - 1) * space_in_px
153
+ available_width = max_total_width - sum_incompressible_space_along_x
154
+ initial_width=available_width
155
+
156
+ total_incompressible_text_width = sum(rect.width(all=True) for rect in rects if rect.isText)
157
+
158
+ compressible = [rect for rect in rects if not rect.isText and not _empty_group(rect)]
159
+ incompressible = [rect for rect in rects if rect.isText]
160
+
161
+ available_width-=total_incompressible_text_width
162
+
163
+ # print('total_incompressible_text_width',total_incompressible_text_width)
164
+ # print(compressible)
165
+ # print(incompressible)
166
+
167
+ # target_height = calculate_target_height(rects, available_width)
168
+
169
+ # total_aspect_ratio = sum(rect.width() / rect.height() for rect in rects if not rect.immutable) # immutable objects should be removed from the stuff -−> TODO
170
+ # total_aspect_ratio = sum(rect.getAR()for rect in rects if not rect.immutable) # immutable objects should be removed from the stuff -−> TODO
171
+ # total_aspect_ratio = sum(rect.getAR() for rect in rects) # immutable objects should be removed from the stuff -−> TODO
172
+
173
+ # print('final compressible', compressible)
174
+
175
+ total_aspect_ratio = sum(rect.getAR() for rect in compressible) # immutable objects should be removed from the stuff -−> TODO
176
+
177
+
178
+ # print('total_aspect_ratio modified',total_aspect_ratio)
179
+ # the idea is that I need to fit the non text stuff there
180
+
181
+
182
+
183
+ # if total_aspect_ratio == 0:
184
+ # print('DEBUG DABU')
185
+ # for rect in rects:
186
+ # print('tata', rect.getAR())
187
+
188
+
189
+ if total_aspect_ratio!=0: # this is just to avoid errors # maybe fit all to current otherwise
190
+ target_height = available_width / total_aspect_ratio
191
+ else:
192
+ target_height = available_width
193
+
194
+ # resized_rects = resize_rects(rects, None, target_height) # --> indeed that works
195
+ resized_compressible_rects = resize_rects(compressible, None, target_height) # --> indeed that works
196
+
197
+ # if resized_compressible_rects:
198
+ # target_width, target_height =
199
+ max_w, max_h, resized_incompressible_rects = resize_incompressible_rects(incompressible, None, target_height) # --> indeed that works
200
+ if compressible:
201
+ height = compressible[0].height(all=True)
202
+ else:
203
+ height = max_h
204
+ if incompressible : # incompressible
205
+ sum_of_widths = sum(rect.width(all=True) for rect in incompressible)
206
+ # Compute the scaling factor
207
+ scaling_factor = initial_width / sum_of_widths
208
+
209
+ # print('total_aspect_ratio in changed',scaling_factor)
210
+ for elm in incompressible:
211
+ elm.setWidth(elm.width(all=True)*scaling_factor)
212
+ elm.set_to_scale(1.)
213
+
214
+ if incompressible:
215
+ for elm in incompressible:
216
+ elm.setHeight(height)
217
+ elm.set_to_scale(1.)
218
+
219
+
220
+ # last thing to do -> if there is no compressible I need adjust the total size to the deisred by keeping height constant and increasing height
221
+ # by the rescaling factor
222
+
223
+
224
+
225
+ return rects # return all rects that have been modified in original order
226
+
227
+
228
+ def resize_rects_within_total_height(rects: list, max_total_height: float, sum_incompressible_space_along_y: float):
229
+ # incompressible_height = (len(rects) - 1) * space_in_px
230
+ available_height = max_total_height - sum_incompressible_space_along_y
231
+ initial_height=available_height
232
+ total_incompressible_text_height = sum(rect.getRect(all=True).height() for rect in rects if rect.isText)
233
+
234
+
235
+ # target_width = calculate_target_width(rects, available_height)
236
+
237
+ # total_aspect_ratio = sum(rect.height() / rect.width() for rect in rects if not rect.immutable) # immutable objects should be removed from the stuff -−> TODO
238
+ # total_aspect_ratio = sum(rect.getAR(invert=True) for rect in rects if not rect.immutable) # immutable objects should be removed from the stuff -−> TODO
239
+
240
+ compressible = [rect for rect in rects if not rect.isText and not _empty_group(rect)]
241
+ incompressible = [rect for rect in rects if rect.isText]
242
+
243
+ available_height -= total_incompressible_text_height
244
+
245
+ # print('total_incompressible_text_height', total_incompressible_text_height)
246
+ # print(compressible)
247
+ # print(incompressible)
248
+
249
+ # target_height = calculate_target_height(rects, available_width)
250
+
251
+ # total_aspect_ratio = sum(rect.width() / rect.height() for rect in rects if not rect.immutable) # immutable objects should be removed from the stuff -−> TODO
252
+ # total_aspect_ratio = sum(rect.getAR()for rect in rects if not rect.immutable) # immutable objects should be removed from the stuff -−> TODO
253
+ # total_aspect_ratio = sum(rect.getAR() for rect in rects) # immutable objects should be removed from the stuff -−> TODO
254
+
255
+
256
+ # total_aspect_ratio = sum(rect.getAR(invert=True) for rect in rects) # immutable objects should be removed from the stuff -−> TODO
257
+ total_aspect_ratio = sum(rect.getAR(invert=True) for rect in compressible) # immutable objects should be removed from the stuff -−> TODO
258
+
259
+ # print('total_aspect_ratio modified', total_aspect_ratio)
260
+
261
+ if total_aspect_ratio != 0: # this is just to avoid errors # maybe fit all to current otherwise
262
+ target_width = available_height / total_aspect_ratio
263
+ else:
264
+ target_width = available_height
265
+
266
+ # target_width = available_height / total_aspect_ratio
267
+
268
+ # resized_rects = resize_rects(rects, target_width,None) # --> indeed that works
269
+ resized_compressible_rects = resize_rects(compressible, target_width, None) # --> indeed that works
270
+ max_w, max_h, resized_incompressible_rects = resize_incompressible_rects(incompressible, target_width,None) # --> indeed that works
271
+ if compressible:
272
+ width = compressible[0].width(all=True)
273
+ else:
274
+ width = max_w
275
+ if incompressible : # incompressible
276
+ sum_of_heights = sum(rect.height(all=True) for rect in incompressible)
277
+ # Compute the scaling factor
278
+ scaling_factor = initial_height / sum_of_heights
279
+
280
+ # print('total_aspect_ratio in chnaged',scaling_factor)
281
+ for elm in incompressible:
282
+ elm.setHeight(elm.height(all=True)*scaling_factor)
283
+ elm.set_to_scale(1.)
284
+
285
+ if incompressible:
286
+ for elm in incompressible:
287
+ elm.setWidth(width)
288
+ elm.set_to_scale(1.)
289
+
290
+ return rects
291
+
292
+
293
+ def resize_rects_within_grid(rects: list, grid_size: tuple, max_total_width: float, max_total_height: float, incompressible_width: float, incompressible_height: float, fit_in_width:int):
294
+ # this is a bit too complicated with grids as they will have complex incompressibility in width and height that will vary depending on the amount of rows and cols !!!!
295
+
296
+ # this sounds like good and simple code!!!
297
+ rows, cols = grid_size
298
+
299
+ # rather than passing the incompressible size I just would need to compute it per object on the fly !!!
300
+
301
+ available_width = max_total_width - (cols - 1) * incompressible_width # NB maybe this should be done per row ????
302
+ available_height = max_total_height - (rows - 1) * incompressible_height
303
+
304
+ target_width = available_width / cols
305
+ target_height = available_height / rows
306
+
307
+ if fit_in_width:
308
+ target_height=None
309
+ else:
310
+ target_width=None
311
+ # here I need to choose between width and height
312
+ # ideally always prefer width except if extends beyond height
313
+ # when resized
314
+ # start with width
315
+
316
+ # in fact I should take either here --> the best fit in width and or in height -−> auto take it
317
+ # print('sizes',target_width, available_width, max_total_width, target_height, available_height, max_total_height) #92.5 370.0 400 193.33333333333334 580.0 600
318
+
319
+ resized_rects = []
320
+ for rect in rects:
321
+ resized_rect = resize_rect_preserve_aspect_ratio(rect, target_width, target_height)
322
+ resized_rects.append(resized_rect)
323
+
324
+ return resized_rects
325
+
326
+
327
+
328
+ # I could make it smarter by reading the incompressible height from the image and from the packing -−> this is why I need a group maybe???
@@ -0,0 +1,336 @@
1
+ from batoolset.settings.global_settings import set_UI # set the UI to qtpy
2
+ set_UI()
3
+ import os
4
+ from qtpy import QtWidgets
5
+ from qtpy.QtCore import QPoint, QPointF, Qt, QRectF
6
+ from qtpy.QtGui import QBrush, QPen, QColor, QTransform
7
+
8
+ from batoolset.tools.logger import TA_logger
9
+
10
+ logger = TA_logger()
11
+
12
+ class Ellipse2D(QtWidgets.QGraphicsEllipseItem):
13
+
14
+ isSet = False
15
+
16
+ def __init__(self, *args,x=0, y=0, width=0, height=0, color=0xFFFFFF, fill_color=None, opacity=1., stroke=0.65, line_style=None,theta=0,__version__=1.0, **kwargs):
17
+ if args:
18
+ super(Ellipse2D, self).__init__(*args)
19
+ else:
20
+ super(Ellipse2D, self).__init__(x,y,width,height)
21
+
22
+ # super(, self).__init__(*args)
23
+ # if not args:
24
+ # self.isSet = False
25
+ # else:
26
+ # self.isSet = True
27
+
28
+ # print('self.rect()',self.rect())
29
+
30
+ self.setRect(self.rect())
31
+ self.color = color
32
+ self.fill_color = fill_color
33
+ self.stroke = stroke
34
+ self.opacity = opacity
35
+ self.scale = 1
36
+ self.translation = QPointF()
37
+ self.line_style = line_style
38
+ # rotation
39
+ self.theta = theta
40
+ self.__version__ = __version__
41
+
42
+ def set_rotation(self, theta):
43
+ self.theta = theta
44
+
45
+ def set_opacity(self, opacity):
46
+ self.opacity = opacity
47
+
48
+ def set_line_style(self,style):
49
+ """
50
+ allows lines to be dashed or dotted or have custom pattern
51
+ :param style: a list of numbers or any of the following Qt.SolidLine, Qt.DashLine, Qt.DashDotLine, Qt.DotLine, Qt.DashDotDotLine but not Qt.CustomDashLine, Qt.CustomDashLine is assumed by default if a list is passed in. None is also a valid value that resets the line --> assume plain line
52
+ :return:
53
+ """
54
+ self.line_style = style
55
+ # if style is a list then assume custom pattern otherwise apply solidline
56
+
57
+ def draw(self, painter, draw=True, parent=None):
58
+ if self.color is None and self.fill_color is None:
59
+ return
60
+
61
+ if draw:
62
+ painter.save()
63
+ painter.setPen(Qt.NoPen)
64
+ painter.setBrush(Qt.NoBrush)
65
+ painter.setOpacity(self.opacity)
66
+ if self.color is not None:
67
+ pen = QPen(QColor(self.color))
68
+ if self.stroke is not None:
69
+ pen.setWidthF(self.stroke)
70
+ if self.line_style is not None:
71
+ if self.line_style in [Qt.SolidLine, Qt.DashLine, Qt.DashDotLine, Qt.DotLine, Qt.DashDotDotLine]:
72
+ pen.setStyle(self.line_style)
73
+ elif isinstance(self.line_style, list):
74
+ pen.setStyle(Qt.CustomDashLine)
75
+ pen.setDashPattern(self.line_style)
76
+ painter.setPen(pen)
77
+ else:
78
+ painter.setPen(Qt.NoPen)
79
+ if self.fill_color is not None:
80
+ painter.setBrush(QBrush(QColor(self.fill_color)))
81
+ rect_to_plot = self.rect().adjusted(0, 0, 0, 0) # just a copy of the rect --> TODO maybe do it in a cleaner way
82
+
83
+ if parent is not None and parent.scale is not None and parent.scale != 1:
84
+ rect_to_plot = self.__get_scaled_rect(rect_to_plot, 1. / parent.scale)
85
+
86
+ if parent is not None:
87
+ rect_to_plot = rect_to_plot.translated(parent.topLeft())
88
+
89
+ # if self.scale is not None and self.scale != 1:
90
+ # # TODO KEEP THE ORDER THIS MUST BE DONE THIS WAY OR IT WILL GENERATE PLENTY OF BUGS...
91
+ # new_width = rect_to_plot.width() * self.scale
92
+ # new_height = rect_to_plot.height() * self.scale
93
+ # # TODO BE EXTREMELY CAREFUL AS SETX AND SETY CAN CHANGE WIDTH AND HEIGHT --> ALWAYS TAKE SIZE BEFORE OTHERWISE THERE WILL BE A PB AND ALWAYS RESET THE SIZE WHEN SETX IS CALLED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
94
+ # # Sets the left edge of the rectangle to the given x coordinate. May change the width, but will never change the right edge of the rectangle. --> NO CLUE WHY SHOULD CHANGE WIDTH THOUGH BUT BE CAREFUL!!!
95
+ # rect_to_plot.setX(rect_to_plot.x() * self.scale)
96
+ # rect_to_plot.setY(rect_to_plot.y() * self.scale)
97
+ # rect_to_plot.setWidth(new_width)
98
+ # rect_to_plot.setHeight(new_height)
99
+ # if self.translation is not None:
100
+ # rect_to_plot.translate(self.translation)
101
+
102
+ # if self.color is not None:
103
+ # painter.drawRect(rect_to_plot)
104
+ # else:
105
+ # painter.fillRect(rect_to_plot, QColor(self.fill_color))
106
+ if self.theta is not None and self.theta != 0:
107
+ painter.translate(rect_to_plot.center())
108
+ painter.rotate(self.theta)
109
+ painter.translate(-rect_to_plot.center())
110
+
111
+ painter.drawEllipse(rect_to_plot)
112
+ painter.restore()
113
+
114
+
115
+ def __get_scaled_rect(self, rect_to_plot, scale):
116
+ rect = QRectF(rect_to_plot.x()*scale, rect_to_plot.y()*scale, rect_to_plot.width()*scale, rect_to_plot.height()*scale)
117
+ return rect
118
+
119
+
120
+ # def fill(self, painter, draw=True):
121
+ # if self.fill_color is None:
122
+ # return
123
+ # if draw:
124
+ # painter.save()
125
+ # painter.setBrush(QBrush(QColor(self.fill_color)))
126
+ # painter.setOpacity(self.opacity)
127
+ # if draw:
128
+ # painter.drawEllipse(self.rect())
129
+ # painter.restore()
130
+ #
131
+ # # TODO pb will draw the shape twice.... ---> because painter drawpolygon is called twice
132
+ #
133
+ # def drawAndFill(self, painter):
134
+ # painter.save()
135
+ # self.draw(painter, draw=False)
136
+ # self.fill(painter, draw=False)
137
+ # painter.drawEllipse(self.rect())
138
+ # painter.restore()
139
+
140
+ def translate(self, *translation):
141
+ # if isinstance(translation[0], (QPointF, QPoint)):
142
+
143
+ # print('translation szouba', translation)
144
+
145
+ if isinstance(translation[0], (int, float)):
146
+ translation = QPointF(translation[0], translation[1])
147
+
148
+ if isinstance(translation, tuple):
149
+ # not sure this is normal that I have to do that but if I don't do it dragging the ellipse in the demo image crashes the soft
150
+ translation= QPointF(translation[0])
151
+
152
+ # print('zoubi', type(translation))
153
+
154
+ self.moveBy(translation.x(), translation.y())
155
+ rect = self.rect()
156
+ rect.translate(translation.x(), translation.y())
157
+ self.setRect(rect)
158
+
159
+
160
+ def add(self, *args):
161
+ p1 = args[0]
162
+ p2 = args[1]
163
+ rect = self.rect()
164
+ rect.setWidth(abs(p1.x()-p2.x()))
165
+ rect.setHeight(abs(p1.y()-p2.y()))
166
+ x = p2.x()
167
+ y = p2.y()
168
+ if p1.x() < p2.x():
169
+ x = p1.x()
170
+ if p1.y() < p2.y():
171
+ y = p1.y()
172
+ rect.setX(x)
173
+ rect.setY(y)
174
+
175
+ self.setRect(rect)
176
+ # self.isSet = True
177
+
178
+ def boundingRect(self):
179
+ # should I return the scaled version or the orig --> think about it...
180
+ rect_to_plot = self.rect().adjusted(0, 0, 0, 0)
181
+ try:
182
+ # print('tada')
183
+ if self.theta is not None and self.theta!=0:
184
+ # print('entering')
185
+ center = rect_to_plot.center()
186
+ # print('entering2')
187
+ t = QTransform().translate(center.x(), center.y()).rotate(self.theta).translate(-center.x(),
188
+ -center.y())
189
+ # print('entering3')
190
+ # self.setTransform(t)
191
+ # self.transform()
192
+
193
+
194
+ # transformed = QRectF(self.rect())
195
+ # print('entering5', transformed)
196
+ # self.resetTransform()
197
+ # print('entering5', rect_to_plot)
198
+ # print('entering4')
199
+ transformed = t.mapRect(rect_to_plot)
200
+
201
+ # self.setTransform(t)
202
+ # self.transform()
203
+ #
204
+ # print(self.shape().boundingRect())
205
+ #
206
+ # # print(self.rect(), transformed)
207
+ #
208
+ # transformed = QRectF(self.shape().boundingRect())
209
+ # # self.resetTransform()
210
+
211
+ # not perfect but ok for now though --> bounds are not sharp at the edges upon rotation
212
+ # print('entering45', transformed)
213
+ return transformed
214
+ except Exception:
215
+ pass
216
+ return rect_to_plot
217
+
218
+ # def get_P1(self):
219
+ def topLeft(self):
220
+ return self.boundingRect().topLeft()
221
+
222
+ def setTopLeft(self, point):
223
+ # TODO
224
+ # def set_P1(self, point):
225
+ rect = self.rect()
226
+ width = rect.width()
227
+ height = rect.height()
228
+ rect.setX(point.x())
229
+ rect.setY(point.y())
230
+ # required due to setX changing width and sety changing height
231
+ rect.setWidth(width)
232
+ rect.setHeight(height)
233
+ self.setRect(rect)
234
+
235
+ def set_to_scale(self, factor):
236
+ self.scale = factor
237
+
238
+ def set_to_translation(self, translation):
239
+ self.translation = translation
240
+
241
+ def __repr__(self):
242
+ class_name = type(self).__name__
243
+ memory_address = hex(id(self))
244
+ return f"{class_name}-{memory_address}"
245
+
246
+ def getRect(self, *args, **kwargs):
247
+ # if args or kwargs:
248
+ # # print('I am called')
249
+ # rect = QRectF(self.x(), self.y(), self.width() / self.scale, self.height() / self.scale)
250
+ # rect = rect.translated(rect.width()/2., rect.height()/2.)
251
+ # # rect.setX(rect.x()-rect.width()/2.)
252
+ # # rect.setY(rect.y()-rect.height()/2.)
253
+ # return rect
254
+ # else:
255
+ # # return super().getRect()
256
+ # return QRectF(*super().getRect())
257
+ return self.boundingRect()
258
+
259
+ def x(self, *args, **kwargs):
260
+ return self.boundingRect().x()
261
+
262
+ def y(self, *args, **kwargs):
263
+ return self.boundingRect().y()
264
+
265
+ def width(self, *args, **kwargs):
266
+ # if args or kwargs:
267
+ # return self.width() / self.scale
268
+ # else:
269
+ # return self.rect().width()
270
+ return self.boundingRect().width()
271
+
272
+ def height(self, *args, **kwargs):
273
+ # if args or kwargs:
274
+ # return self.height() / self.scale
275
+ # else:
276
+ # # return super().height()
277
+ # return self.rect().height()
278
+ return self.boundingRect().height()
279
+
280
+ def to_dict(self):
281
+ x = self.rect().x()
282
+ y = self.rect().y()
283
+ width = self.rect().width()
284
+ height = self.rect().height()
285
+
286
+ # Create a dictionary representation of the values of the super object
287
+ output_dict = {
288
+ 'x': x,
289
+ 'y': y,
290
+ 'width': width,
291
+ 'height': height,
292
+ }
293
+ # Update the dictionary with the __dict__ of Rectangle2D
294
+ output_dict.update(self.__dict__)
295
+ return output_dict
296
+
297
+
298
+ if __name__ == '__main__':
299
+ # ça marche --> voici deux examples de shapes
300
+ test = Ellipse2D(0, 0, 100, 100)
301
+ # print(test.x(), test.y(), test.width(), test.height())
302
+ print(test.contains(QPointF(50, 50)))
303
+ print(test.contains(QPointF(15, 15)))
304
+ print(test.contains(QPointF(-1, -1)))
305
+ print(test.contains(QPointF(0, 0)))
306
+ print(test.contains(QPointF(100, 100)))
307
+ print(test.contains(QPointF(100, 100.1)))
308
+ print(test.x())
309
+ print(test.y())
310
+ print(test.translate(QPoint(10, 10)))
311
+ print(test.x())
312
+ print(test.y())
313
+
314
+ # p1 = test.p1()
315
+ # print(p1.x(), p1.y())
316
+ # p2 = test.p2()
317
+ # print(p2.x(), p2.y())
318
+ # print(test.arrow)
319
+ # print(test.length()) # sqrt 2 --> 141
320
+ # # if it's an arrow I can add easily all the stuff I need
321
+ #
322
+ # test = Rect2D(0, 0, 1, 1)
323
+ # p1 = test.p1()
324
+ # print(p1.x(), p1.y())
325
+ # p2 = test.p2()
326
+ # print(p2.x(), p2.y())
327
+ # print(test.arrow)
328
+ # import math
329
+ # print(test.length() == math.sqrt(2)) # sqrt 2
330
+ #
331
+ # test2 = Rect2D()
332
+ # p1 = test2.p1()
333
+ # print(p1.x(), p1.y())
334
+ # p2 = test2.p2()
335
+ # print(p2.x(), p2.y())
336
+ # print(test2.arrow)