molde 0.1.19__tar.gz

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 (68) hide show
  1. molde-0.1.19/PKG-INFO +46 -0
  2. molde-0.1.19/README.md +26 -0
  3. molde-0.1.19/molde/__init__.py +14 -0
  4. molde-0.1.19/molde/__main__.py +81 -0
  5. molde-0.1.19/molde/actors/__init__.py +5 -0
  6. molde-0.1.19/molde/actors/common_symbols_actor.py +148 -0
  7. molde-0.1.19/molde/actors/ghost_actor.py +12 -0
  8. molde-0.1.19/molde/actors/lines_actor.py +31 -0
  9. molde-0.1.19/molde/actors/round_points_actor.py +7 -0
  10. molde-0.1.19/molde/actors/square_points_actor.py +32 -0
  11. molde-0.1.19/molde/colors/__init__.py +2 -0
  12. molde-0.1.19/molde/colors/color.py +265 -0
  13. molde-0.1.19/molde/colors/color_names.py +125 -0
  14. molde-0.1.19/molde/fonts/IBMPlexMono-Bold.ttf +0 -0
  15. molde-0.1.19/molde/fonts/IBMPlexMono-Regular.ttf +0 -0
  16. molde-0.1.19/molde/icons/arrow_down_dark_theme.svg +1 -0
  17. molde-0.1.19/molde/icons/arrow_down_disabled_dark_theme.svg +1 -0
  18. molde-0.1.19/molde/icons/arrow_down_disabled_light_theme.svg +1 -0
  19. molde-0.1.19/molde/icons/arrow_down_light_theme.svg +1 -0
  20. molde-0.1.19/molde/icons/arrow_left_dark_theme.svg +1 -0
  21. molde-0.1.19/molde/icons/arrow_left_light_theme.svg +1 -0
  22. molde-0.1.19/molde/icons/arrow_right_dark_theme.svg +1 -0
  23. molde-0.1.19/molde/icons/arrow_right_light_theme.svg +1 -0
  24. molde-0.1.19/molde/icons/arrow_up_dark_theme.svg +1 -0
  25. molde-0.1.19/molde/icons/arrow_up_disabled_dark_theme.svg +1 -0
  26. molde-0.1.19/molde/icons/arrow_up_disabled_light_theme.svg +1 -0
  27. molde-0.1.19/molde/icons/arrow_up_light_theme.svg +1 -0
  28. molde-0.1.19/molde/icons/check_box_image.svg +1 -0
  29. molde-0.1.19/molde/interactor_styles/__init__.py +2 -0
  30. molde-0.1.19/molde/interactor_styles/arcball_camera_style.py +334 -0
  31. molde-0.1.19/molde/interactor_styles/box_selection_style.py +87 -0
  32. molde-0.1.19/molde/main_window.ui +864 -0
  33. molde-0.1.19/molde/pickers/__init__.py +2 -0
  34. molde-0.1.19/molde/pickers/cell_area_picker.py +61 -0
  35. molde-0.1.19/molde/pickers/cell_property_area_picker.py +84 -0
  36. molde-0.1.19/molde/poly_data/__init__.py +13 -0
  37. molde-0.1.19/molde/poly_data/arrows.py +54 -0
  38. molde-0.1.19/molde/poly_data/lines_data.py +23 -0
  39. molde-0.1.19/molde/poly_data/simple_shapes.py +22 -0
  40. molde-0.1.19/molde/poly_data/vertices_data.py +24 -0
  41. molde-0.1.19/molde/render_widgets/__init__.py +2 -0
  42. molde-0.1.19/molde/render_widgets/animated_render_widget.py +164 -0
  43. molde-0.1.19/molde/render_widgets/common_render_widget.py +451 -0
  44. molde-0.1.19/molde/stylesheets/__init__.py +122 -0
  45. molde-0.1.19/molde/stylesheets/common.qss +16 -0
  46. molde-0.1.19/molde/stylesheets/create_color_page.py +61 -0
  47. molde-0.1.19/molde/stylesheets/mainwindow.ui +646 -0
  48. molde-0.1.19/molde/stylesheets/qcheckbox.qss +24 -0
  49. molde-0.1.19/molde/stylesheets/qinputs.qss +82 -0
  50. molde-0.1.19/molde/stylesheets/qlayouts.qss +23 -0
  51. molde-0.1.19/molde/stylesheets/qmenubar.qss +12 -0
  52. molde-0.1.19/molde/stylesheets/qprogressbar.qss +12 -0
  53. molde-0.1.19/molde/stylesheets/qpushbutton.qss +89 -0
  54. molde-0.1.19/molde/stylesheets/qradiobutton.qss +31 -0
  55. molde-0.1.19/molde/stylesheets/qscrollbar.qss +30 -0
  56. molde-0.1.19/molde/stylesheets/qslider.qss +61 -0
  57. molde-0.1.19/molde/stylesheets/qtablewidget.qss +27 -0
  58. molde-0.1.19/molde/stylesheets/qtabwidget.qss +29 -0
  59. molde-0.1.19/molde/stylesheets/qtoolbar.qss +63 -0
  60. molde-0.1.19/molde/stylesheets/qtoolbuttons.qss +14 -0
  61. molde-0.1.19/molde/stylesheets/qtreewidget.qss +25 -0
  62. molde-0.1.19/molde/ui_files/messages/new_loading_window.ui +73 -0
  63. molde-0.1.19/molde/utils/__init__.py +8 -0
  64. molde-0.1.19/molde/utils/format_sequences.py +44 -0
  65. molde-0.1.19/molde/utils/poly_data_utils.py +66 -0
  66. molde-0.1.19/molde/utils/tree_info.py +52 -0
  67. molde-0.1.19/molde/windows/loading_window.py +189 -0
  68. molde-0.1.19/pyproject.toml +28 -0
molde-0.1.19/PKG-INFO ADDED
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: molde
3
+ Version: 0.1.19
4
+ Summary:
5
+ Author: André Fernandes
6
+ Author-email: fpf.andre@gmail.com
7
+ Requires-Python: >=3.10
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: moviepy (>=1.0.3,<2.0.0)
15
+ Requires-Dist: numpy (>=2.0,<3.0)
16
+ Requires-Dist: qtpy (>=2.4.2,<3.0.0)
17
+ Requires-Dist: vtk (>=9.3.1,<10.0.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ <div align="center">
21
+ <img height="90" src="logos/mold_horizontal_logo_dark_300dpi.png" alt="Logo" align="center">
22
+ </div>
23
+
24
+ # MOLDE
25
+ **MO**PT **L**ightweight **De**sign (MOLDE) is a library created to standardize and make easier the creation and maintainance of internal products of the Multidisciplinary Optimization Group (MOPT).
26
+
27
+ Here are the colors, icons, fonts and common interface components, that may be usefull for multiple MOPT projects.
28
+
29
+ This is heavily inspired by [Carbon Design System](https://carbondesignsystem.com/), [Atlassian Design System](https://atlassian.design/), [Fast Design System](https://www.fast.design/), [Material Design](https://m3.material.io/) and [Bold Design System](https://bold.bridge.ufsc.br/pt/).
30
+
31
+ # Colors
32
+ The color palettes are available [here](https://andrefpf.github.io/molde/).
33
+
34
+ # Icons
35
+ A lot of free to use icons, from Material Design, are available [here](https://fonts.google.com/icons).
36
+ Other icons may be necessary, and they will be made to match the same style.
37
+ For consistency avoid using icons from other places.
38
+
39
+ # Typeface
40
+ The font choose for this system is [IBM Plex](https://www.ibm.com/plex/).
41
+ The `.ttf` files for every version of the font, including monospaced, can be found [here](https://github.com/IBM/plex/tree/master/packages).
42
+
43
+ # Component
44
+ The sylesheets of the Qt components are designed to make it similar to [Atlassian components](https://atlassian.design/components/).
45
+ Always check for the Atlassian component usage guide in case of any doubt.
46
+
molde-0.1.19/README.md ADDED
@@ -0,0 +1,26 @@
1
+ <div align="center">
2
+ <img height="90" src="logos/mold_horizontal_logo_dark_300dpi.png" alt="Logo" align="center">
3
+ </div>
4
+
5
+ # MOLDE
6
+ **MO**PT **L**ightweight **De**sign (MOLDE) is a library created to standardize and make easier the creation and maintainance of internal products of the Multidisciplinary Optimization Group (MOPT).
7
+
8
+ Here are the colors, icons, fonts and common interface components, that may be usefull for multiple MOPT projects.
9
+
10
+ This is heavily inspired by [Carbon Design System](https://carbondesignsystem.com/), [Atlassian Design System](https://atlassian.design/), [Fast Design System](https://www.fast.design/), [Material Design](https://m3.material.io/) and [Bold Design System](https://bold.bridge.ufsc.br/pt/).
11
+
12
+ # Colors
13
+ The color palettes are available [here](https://andrefpf.github.io/molde/).
14
+
15
+ # Icons
16
+ A lot of free to use icons, from Material Design, are available [here](https://fonts.google.com/icons).
17
+ Other icons may be necessary, and they will be made to match the same style.
18
+ For consistency avoid using icons from other places.
19
+
20
+ # Typeface
21
+ The font choose for this system is [IBM Plex](https://www.ibm.com/plex/).
22
+ The `.ttf` files for every version of the font, including monospaced, can be found [here](https://github.com/IBM/plex/tree/master/packages).
23
+
24
+ # Component
25
+ The sylesheets of the Qt components are designed to make it similar to [Atlassian components](https://atlassian.design/components/).
26
+ Always check for the Atlassian component usage guide in case of any doubt.
@@ -0,0 +1,14 @@
1
+ from pathlib import Path
2
+ from qtpy.uic import loadUi
3
+ from .colors import Color
4
+
5
+ MOLDE_DIR = Path(__file__).parent
6
+ UI_DIR = MOLDE_DIR / "ui_files/"
7
+
8
+ def load_ui(uifile: str | Path, baseinstance):
9
+ from PySide6.QtCore import QDir
10
+
11
+ working_directory = str(Path(uifile).parent)
12
+ working_directory = QDir(working_directory)
13
+
14
+ return loadUi(uifile, baseinstance, working_directory)
@@ -0,0 +1,81 @@
1
+ import os, sys
2
+ from PySide6.QtWidgets import QMainWindow, QApplication, QMessageBox, QLineEdit, QTableWidgetItem, QPushButton, QLabel, QSlider
3
+ from PySide6.QtGui import QColor, QFont
4
+ from PySide6.QtCore import Qt
5
+ from time import time
6
+
7
+ from vtkmodules.vtkFiltersSources import vtkCylinderSource
8
+ from vtkmodules.vtkRenderingCore import vtkPolyDataMapper, vtkActor
9
+
10
+ from molde import MOLDE_DIR, load_ui
11
+ from molde import stylesheets
12
+ from molde.render_widgets.common_render_widget import CommonRenderWidget
13
+
14
+
15
+ class Example(QMainWindow):
16
+ def __init__(self, parent=None) -> None:
17
+ super().__init__()
18
+ load_ui(MOLDE_DIR / "stylesheets/mainwindow.ui", self, MOLDE_DIR / "stylesheets")
19
+ self.current_theme = "light"
20
+
21
+ self.change_theme_button.clicked.connect(self.change_theme)
22
+ # self.render_widget: CommonRenderWidget
23
+ # self.render_widget.create_axes()
24
+ # self.render_widget.create_scale_bar()
25
+ # self.render_widget.create_color_bar()
26
+ # self.render_widget.set_info_text("Hola\nque\ntal?")
27
+
28
+ cylinder = vtkCylinderSource()
29
+ cylinder.SetResolution(8)
30
+ cylinder_mapper = vtkPolyDataMapper()
31
+ cylinder_mapper.SetInputConnection(cylinder.GetOutputPort())
32
+ cylinder_actor = vtkActor()
33
+ cylinder_actor.SetMapper(cylinder_mapper)
34
+ # self.render_widget.add_actors(cylinder_actor)
35
+
36
+ self.botao1 = QPushButton()
37
+ self.label = QLabel("Olha o sapooo")
38
+ self.label.setProperty("type", "logo")
39
+ # font = QFont()
40
+ # font.setFamily("Bauhaus 93")
41
+ # self.label.setFont(font)
42
+ print(self.label.fontInfo().family())
43
+
44
+ self.slider = QSlider(Qt.Orientation.Horizontal)
45
+ self.slider.setValue(50)
46
+ self.botao1.setText("Olha a faca")
47
+ self.toolbar_2.addWidget(self.label)
48
+ self.toolbar_2.setDisabled(False)
49
+
50
+ item = QTableWidgetItem("fr")
51
+ item.setBackground(QColor("#FF0000"))
52
+ self.tableWidget.setItem(0, 0, item)
53
+ self.show()
54
+
55
+ def change_theme(self):
56
+ if self.current_theme == "light":
57
+ self.current_theme = "dark"
58
+ else:
59
+ self.current_theme = "light"
60
+
61
+ # self.render_widget.set_theme(self.current_theme)
62
+ stylesheets.set_theme(self.current_theme)
63
+
64
+ def closeEvent(self, event):
65
+ close = QMessageBox.question(
66
+ self,
67
+ "QUIT",
68
+ "Would you like to close the application?",
69
+ QMessageBox.Yes | QMessageBox.No
70
+ )
71
+ QApplication.quit()
72
+
73
+
74
+ if __name__ == "__main__":
75
+ # Make the window scale evenly for every monitor
76
+ os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
77
+
78
+ app = QApplication(sys.argv)
79
+ e = Example()
80
+ e.change_theme()
81
+ sys.exit(app.exec())
@@ -0,0 +1,5 @@
1
+ from .ghost_actor import GhostActor
2
+ from .lines_actor import LinesActor
3
+ from .round_points_actor import RoundPointsActor
4
+ from .square_points_actor import SquarePointsActor
5
+ from .common_symbols_actor import CommonSymbolsActorFixedSize, CommonSymbolsActorVariableSize
@@ -0,0 +1,148 @@
1
+ from dataclasses import dataclass
2
+ from typing import Callable
3
+
4
+ from vtkmodules.vtkCommonCore import (
5
+ vtkDoubleArray,
6
+ vtkIntArray,
7
+ vtkPoints,
8
+ vtkUnsignedCharArray,
9
+ )
10
+ from vtkmodules.vtkCommonDataModel import vtkPolyData
11
+ from vtkmodules.vtkRenderingCore import (
12
+ vtkActor,
13
+ vtkDistanceToCamera,
14
+ vtkGlyph3DMapper,
15
+ vtkRenderer,
16
+ )
17
+
18
+ from molde import Color
19
+
20
+ Triple = tuple[float, float, float]
21
+
22
+
23
+ @dataclass
24
+ class Symbol:
25
+ shape_function: Callable
26
+ position: Triple
27
+ orientation: Triple
28
+ color: Color
29
+ scale: float
30
+
31
+
32
+ class CommonSymbolsActor(vtkActor):
33
+ def __init__(self, *args, **kwargs):
34
+ self._symbols: list[Symbol] = list()
35
+
36
+ def add_symbol(
37
+ self,
38
+ shape_function: Callable,
39
+ position: Triple,
40
+ orientation: Triple,
41
+ color: Triple,
42
+ scale: float = 1,
43
+ ):
44
+ symbol = Symbol(
45
+ shape_function,
46
+ position,
47
+ orientation,
48
+ color,
49
+ scale,
50
+ )
51
+ self._symbols.append(symbol)
52
+
53
+ def clear_symbols(self):
54
+ self._symbols.clear()
55
+
56
+ def common_build(self):
57
+ self.data = vtkPolyData()
58
+ points = vtkPoints()
59
+
60
+ self.mapper: vtkGlyph3DMapper = vtkGlyph3DMapper()
61
+ self.SetMapper(self.mapper)
62
+
63
+ sources = vtkIntArray()
64
+ sources.SetName("sources")
65
+
66
+ rotations = vtkDoubleArray()
67
+ rotations.SetNumberOfComponents(3)
68
+ rotations.SetName("rotations")
69
+
70
+ scales = vtkDoubleArray()
71
+ scales.SetName("scales")
72
+
73
+ colors = vtkUnsignedCharArray()
74
+ colors.SetNumberOfComponents(4)
75
+ colors.SetName("colors")
76
+
77
+ shape_function_to_index = dict()
78
+ for symbol in self._symbols:
79
+ if symbol.shape_function not in shape_function_to_index:
80
+ index = len(shape_function_to_index)
81
+ shape_function_to_index[symbol.shape_function] = index
82
+ self.mapper.SetSourceData(index, symbol.shape_function())
83
+
84
+ points.InsertNextPoint(symbol.position)
85
+ rotations.InsertNextTuple(symbol.orientation)
86
+ colors.InsertNextTuple(symbol.color.to_rgba())
87
+ scales.InsertNextValue(symbol.scale)
88
+ sources.InsertNextValue(shape_function_to_index[symbol.shape_function])
89
+
90
+ self.data.SetPoints(points)
91
+ self.data.GetPointData().AddArray(sources)
92
+ self.data.GetPointData().AddArray(rotations)
93
+ self.data.GetPointData().AddArray(scales)
94
+ self.data.GetPointData().SetScalars(colors)
95
+
96
+ def set_zbuffer_offsets(self, factor: float, units: float):
97
+ """
98
+ This functions is usefull to make a object appear in front of the others.
99
+ If the object should never be hidden, the parameters should be set to
100
+ factor = 1 and offset = -66000.
101
+ """
102
+ self.mapper.SetResolveCoincidentTopologyToPolygonOffset()
103
+ self.mapper.SetRelativeCoincidentTopologyLineOffsetParameters(factor, units)
104
+ self.mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(factor, units)
105
+ self.mapper.SetRelativeCoincidentTopologyPointOffsetParameter(units)
106
+ self.mapper.Update()
107
+
108
+
109
+ class CommonSymbolsActorFixedSize(CommonSymbolsActor):
110
+ def build(self):
111
+ self.common_build()
112
+
113
+ self.mapper.SetInputData(self.data)
114
+ self.mapper.SetSourceIndexArray("sources")
115
+ self.mapper.SetOrientationArray("rotations")
116
+ self.mapper.SetScaleArray("scales")
117
+ self.mapper.SourceIndexingOn()
118
+ self.mapper.ScalarVisibilityOn()
119
+ self.mapper.SetScaleModeToScaleByMagnitude()
120
+ self.mapper.SetScalarModeToUsePointData()
121
+ self.mapper.SetOrientationModeToDirection()
122
+ self.mapper.Update()
123
+
124
+
125
+ class CommonSymbolsActorVariableSize(CommonSymbolsActor):
126
+ def __init__(self, renderer: vtkRenderer):
127
+ super().__init__()
128
+ self.renderer = renderer
129
+
130
+ def build(self):
131
+ self.common_build()
132
+
133
+ distance_to_camera = vtkDistanceToCamera()
134
+ distance_to_camera.SetInputData(self.data)
135
+ distance_to_camera.SetScreenSize(40)
136
+ distance_to_camera.SetRenderer(self.renderer)
137
+
138
+ self.mapper.SetInputConnection(distance_to_camera.GetOutputPort())
139
+ self.mapper.SetSourceIndexArray("sources")
140
+ self.mapper.SetOrientationArray("rotations")
141
+ self.mapper.SetScaleArray("DistanceToCamera")
142
+ self.mapper.SourceIndexingOn()
143
+ self.mapper.ScalarVisibilityOn()
144
+ self.mapper.SetScaleModeToScaleByMagnitude()
145
+ self.mapper.SetScalarModeToUsePointData()
146
+ self.mapper.SetOrientationModeToDirection()
147
+
148
+ self.mapper.Update()
@@ -0,0 +1,12 @@
1
+ from vtkmodules.vtkRenderingCore import vtkActor
2
+
3
+
4
+ class GhostActor(vtkActor):
5
+ def make_ghost(self):
6
+ self.GetProperty().LightingOff()
7
+ offset = -66000
8
+ mapper = self.GetMapper()
9
+ mapper.SetResolveCoincidentTopologyToPolygonOffset()
10
+ mapper.SetRelativeCoincidentTopologyLineOffsetParameters(0, offset)
11
+ mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(0, offset)
12
+ mapper.SetRelativeCoincidentTopologyPointOffsetParameter(offset)
@@ -0,0 +1,31 @@
1
+ from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper
2
+
3
+ from molde.poly_data import LinesData
4
+
5
+
6
+ class LinesActor(vtkActor):
7
+ def __init__(self, lines_list) -> None:
8
+ super().__init__()
9
+ self.lines_list = lines_list
10
+
11
+ self.build()
12
+
13
+ def build(self):
14
+ data = LinesData(self.lines_list)
15
+ mapper = vtkPolyDataMapper()
16
+ mapper.SetInputData(data)
17
+ self.SetMapper(mapper)
18
+ self.GetProperty().SetLineWidth(3)
19
+
20
+ def set_width(self, width):
21
+ self.GetProperty().SetLineWidth(width)
22
+
23
+ def appear_in_front(self, cond: bool):
24
+ # this offset is the Z position of the camera buffer.
25
+ # if it is -66000 the object stays in front of everything.
26
+ offset = -66000 if cond else 0
27
+ mapper = self.GetMapper()
28
+ mapper.SetResolveCoincidentTopologyToPolygonOffset()
29
+ mapper.SetRelativeCoincidentTopologyLineOffsetParameters(0, offset)
30
+ mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(0, offset)
31
+ mapper.SetRelativeCoincidentTopologyPointOffsetParameter(offset)
@@ -0,0 +1,7 @@
1
+ from .square_points_actor import SquarePointsActor
2
+
3
+
4
+ class RoundPointsActor(SquarePointsActor):
5
+ def __init__(self, points) -> None:
6
+ super().__init__(points)
7
+ self.GetProperty().RenderPointsAsSpheresOn()
@@ -0,0 +1,32 @@
1
+ from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper
2
+
3
+ from molde.poly_data import VerticesData
4
+
5
+
6
+ class SquarePointsActor(vtkActor):
7
+ def __init__(self, points_list) -> None:
8
+ super().__init__()
9
+ self.points_list = points_list
10
+ self.build()
11
+
12
+ def build(self):
13
+ data = VerticesData(self.points_list)
14
+ mapper = vtkPolyDataMapper()
15
+ mapper.SetInputData(data)
16
+ self.SetMapper(mapper)
17
+
18
+ self.GetProperty().SetPointSize(20)
19
+ self.GetProperty().LightingOff()
20
+
21
+ def set_size(self, size):
22
+ self.GetProperty().SetPointSize(size)
23
+
24
+ def appear_in_front(self, cond: bool):
25
+ # this offset is the Z position of the camera buffer.
26
+ # if it is -66000 the object stays in front of everything.
27
+ offset = -66000 if cond else 0
28
+ mapper = self.GetMapper()
29
+ mapper.SetResolveCoincidentTopologyToPolygonOffset()
30
+ mapper.SetRelativeCoincidentTopologyLineOffsetParameters(0, offset)
31
+ mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(0, offset)
32
+ mapper.SetRelativeCoincidentTopologyPointOffsetParameter(offset)
@@ -0,0 +1,2 @@
1
+ from .color import Color
2
+ from .color_names import *
@@ -0,0 +1,265 @@
1
+ import typing
2
+ from qtpy.QtGui import QColor
3
+ import numpy as np
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass(frozen=True, init=False)
8
+ class Color:
9
+ r: int
10
+ g: int
11
+ b: int
12
+ a: int
13
+
14
+ @typing.overload
15
+ def __new__(self, r: int, g: int, b: int, a: int = 255):
16
+ """
17
+ Initialize Colors with RGB or RGBA integer values ranging from 0 to 255.
18
+ """
19
+
20
+ @typing.overload
21
+ def __new__(self, r: float, g: float, b: float, a: float = 1.0):
22
+ """
23
+ Initialize Colors with RGB or RGBA floating values ranging from 0.0 to 1.0.
24
+ """
25
+
26
+ @typing.overload
27
+ def __new__(self, hexa: str):
28
+ """
29
+ Initialize colors from hex values.
30
+ The valid formats incluce RGB (#FF0000 for example)
31
+ and RGBA (#FF0000FF for example)
32
+ """
33
+
34
+ @typing.overload
35
+ def __new__(self, qcolor: QColor):
36
+ """
37
+ Initialize the color class with an instance of QColor
38
+ """
39
+
40
+ @typing.overload
41
+ def __new__(self, color: "Color"):
42
+ """
43
+ Initialize the color class with an instance it's own class
44
+ """
45
+
46
+ @typing.overload
47
+ def __new__(self):
48
+ """
49
+ Initialize an empty black color
50
+ """
51
+
52
+ def __new__(cls, *args):
53
+ """
54
+ Calls the appropriate constructor for the class according to the
55
+ number of arguments and their types.
56
+ """
57
+
58
+ all_int = all([isinstance(i, int) for i in args])
59
+ all_float = all([isinstance(i, float) for i in args])
60
+
61
+ if len(args) == 0:
62
+ return cls.from_rgba(0, 0, 0, 255)
63
+
64
+ elif len(args) == 1 and isinstance(args[0], str):
65
+ return cls.from_hex(*args)
66
+
67
+ elif len(args) == 1 and isinstance(args[0], QColor):
68
+ return cls.from_qcolor(*args)
69
+
70
+ elif len(args) == 1 and isinstance(args[0], Color):
71
+ return cls.from_color(*args)
72
+
73
+ elif len(args) in [3, 4] and all_int:
74
+ return cls.from_rgba(*args)
75
+
76
+ elif len(args) in [3, 4] and all_float:
77
+ return cls.from_rgba_f(*args)
78
+
79
+ else:
80
+ raise ValueError("Invalid input values")
81
+
82
+ @classmethod
83
+ def from_rgba(cls, r: int, g: int, b: int, a: int = 255) -> "Color":
84
+ """
85
+ This is the default constructor for the class.
86
+ All other constructors call this one.
87
+
88
+ Since "Color" should be imutable, we use "object.__setattr__" to bypass
89
+ the imutability just in the constructor.
90
+ """
91
+
92
+ obj = super().__new__(cls)
93
+ object.__setattr__(obj, "r", round(np.clip(r, 0, 255)))
94
+ object.__setattr__(obj, "g", round(np.clip(g, 0, 255)))
95
+ object.__setattr__(obj, "b", round(np.clip(b, 0, 255)))
96
+ object.__setattr__(obj, "a", round(np.clip(a, 0, 255)))
97
+ return obj
98
+
99
+ @classmethod
100
+ def from_rgb(self, r: int, g: int, b: int) -> "Color":
101
+ return self.from_rgba(r, g, b)
102
+
103
+ @classmethod
104
+ def from_rgb_f(self, r: float, g: float, b: float) -> "Color":
105
+ return self.from_rgba_f(r, g, b)
106
+
107
+ @classmethod
108
+ def from_rgba_f(self, r: float, g: float, b: float, a: float = 1) -> "Color":
109
+ return self.from_rgba(
110
+ round(r * 255),
111
+ round(g * 255),
112
+ round(b * 255),
113
+ round(a * 255),
114
+ )
115
+
116
+ @classmethod
117
+ def from_hex(self, color: str) -> "Color":
118
+ color = color.lstrip("#")
119
+ if len(color) == 6:
120
+ r, g, b = int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16)
121
+ return self.from_rgb(r, g, b)
122
+ elif len(color) == 8:
123
+ r, g, b, a = int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16), int(color[6:8], 16)
124
+ return self.from_rgba(r, g, b, a)
125
+ raise ValueError("Invalid hex color format")
126
+
127
+ @classmethod
128
+ def from_hsv(self, hue: float, saturation: float, value: float):
129
+ v = value / 100
130
+ s = saturation / 100
131
+
132
+ c = v * s
133
+ h = hue % 360 / 60
134
+ x = c * (1 - abs(h % 2 - 1))
135
+
136
+ if 0 <= h < 1:
137
+ r, g, b = c, x, 0
138
+ elif 1 <= h < 2:
139
+ r, g, b = x, c, 0
140
+ elif 2 <= h < 3:
141
+ r, g, b = 0, c, x
142
+ elif 3 <= h < 4:
143
+ r, g, b = 0, x, c
144
+ elif 4 <= h < 5:
145
+ r, g, b = x, 0, c
146
+ elif 5 <= h < 6:
147
+ r, g, b = c, 0, x
148
+ else:
149
+ r, g, b = 0, 0, 0
150
+
151
+ return self.from_rgb_f(
152
+ (r + v - c),
153
+ (g + v - c),
154
+ (b + v - c),
155
+ )
156
+
157
+ @classmethod
158
+ def from_qcolor(self, color: QColor) -> "Color":
159
+ return self.from_rgba(color.red(), color.green(), color.blue(), color.alpha())
160
+
161
+ @classmethod
162
+ def from_color(self, color: "Color"):
163
+ self.r = color.r
164
+ self.g = color.g
165
+ self.b = color.b
166
+ self.a = color.a
167
+ return self
168
+
169
+ def to_rgb(self) -> tuple[int, int, int]:
170
+ return (self.r, self.g, self.b)
171
+
172
+ def to_rgba(self) -> tuple[int, int, int, int]:
173
+ return (self.r, self.g, self.b, self.a)
174
+
175
+ def to_rgb_f(self) -> tuple[float, float, float]:
176
+ return ((self.r / 255), (self.g / 255), (self.b / 255))
177
+
178
+ def to_rgba_f(self) -> tuple[float, float, float, float]:
179
+ return ((self.r / 255), (self.g / 255), (self.b / 255), (self.a / 255))
180
+
181
+ def to_hex(self) -> str:
182
+ return f"#{self.r:02X}{self.g:02X}{self.b:02X}"
183
+
184
+ def to_hexa(self) -> str:
185
+ return f"#{self.r:02X}{self.g:02X}{self.b:02X}{self.a:02X}"
186
+
187
+ def to_hsv(self) -> tuple[int, int, int]:
188
+ r, g, b = self.to_rgb_f()
189
+ min_, mid_, max_ = sorted((r, g, b))
190
+ delta = max_ - min_
191
+
192
+ if delta == 0:
193
+ hue = 0
194
+ elif max_ == r:
195
+ hue = (g - b) / delta
196
+ elif max_ == g:
197
+ hue = (b - r) / delta + 2
198
+ elif max_ == b:
199
+ hue = (r - g) / delta + 4
200
+ else:
201
+ hue = 0
202
+
203
+ hue = round(60 * hue)
204
+ value = round(100 * max_)
205
+ saturation = round(100 * delta / max_) if (max_ != 0) else 0
206
+
207
+ return (
208
+ np.clip(0, 360, hue),
209
+ np.clip(0, 100, saturation),
210
+ np.clip(0, 100, value),
211
+ )
212
+
213
+ def to_qt(self) -> QColor:
214
+ return QColor(self.r, self.g, self.b, self.a)
215
+
216
+ def copy(self) -> "Color":
217
+ return Color(self.r, self.g, self.b, self.a)
218
+
219
+ def apply_factor(self, factor: float | int) -> "Color":
220
+ r = round(np.clip(self.r * factor, 0, 255))
221
+ g = round(np.clip(self.g * factor, 0, 255))
222
+ b = round(np.clip(self.b * factor, 0, 255))
223
+ return Color.from_rgba(r, g, b, self.a)
224
+
225
+ def with_brightness(self, brightness: int) -> "Color":
226
+ """
227
+ Percentage of brightness of the new color.
228
+ """
229
+ h, s, _ = self.to_hsv()
230
+ return self.from_hsv(h, s, brightness)
231
+
232
+ def with_saturation(self, saturation: int) -> "Color":
233
+ """
234
+ Percentage of brightness of the new color.
235
+ """
236
+ h, _, v = self.to_hsv()
237
+ return self.from_hsv(h, saturation, v)
238
+
239
+ def with_rgba(
240
+ self,
241
+ r: int | None = None,
242
+ g: int | None = None,
243
+ b: int | None = None,
244
+ a: int | None = None,
245
+ ) -> "Color":
246
+ return self.from_rgba(
247
+ r if (r is not None) else self.r,
248
+ g if (g is not None) else self.g,
249
+ b if (b is not None) else self.b,
250
+ a if (a is not None) else self.a,
251
+ )
252
+
253
+ def with_rgba_f(
254
+ self,
255
+ r: int | None = None,
256
+ g: int | None = None,
257
+ b: int | None = None,
258
+ a: int | None = None,
259
+ ) -> "Color":
260
+ return self.from_rgba_f(
261
+ r if (r is not None) else self.r,
262
+ g if (g is not None) else self.g,
263
+ b if (b is not None) else self.b,
264
+ a if (a is not None) else self.a,
265
+ )