summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaweł Redman <pawel.redman@gmail.com>2019-04-28 17:29:18 +0200
committerPaweł Redman <pawel.redman@gmail.com>2019-04-28 19:07:20 +0200
commit52a8a95a8553f5b2a1224db474e6cf1d649533c8 (patch)
tree003e0c94839b6daebef19049c75ba0ed43082a7b
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--src/file.py44
-rw-r--r--src/main.py19
-rw-r--r--src/phys.py99
-rw-r--r--src/ui.py421
-rw-r--r--src/ui_widgets.py226
6 files changed, 810 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c18dd8d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__/
diff --git a/src/file.py b/src/file.py
new file mode 100644
index 0000000..4285207
--- /dev/null
+++ b/src/file.py
@@ -0,0 +1,44 @@
+import re, sys, traceback, json
+import phys
+
+file_format_version = 1
+
+def save_system(path, system):
+ ser = dict()
+ ser["version"] = file_format_version
+
+ ser["ignore"] = system.ignore
+ ser["elements"] = []
+ for pol in system.elements:
+ el = {
+ "type": pol.type,
+ "angle": pol.angle,
+ "delta": pol.delta,
+ "ref": pol.ref
+ }
+
+ ser["elements"].append(el)
+
+ with open(path, "w") as fd:
+ fd.write(json.dumps(ser))
+
+def open_system(path):
+ system = phys.System()
+
+ with open(path, "r") as fd:
+ ser = json.loads(fd.read())
+
+ if ser["version"] != file_format_version:
+ raise ValueError("Bad file version: expected %d, found %d" \
+ % (file_format_version, ser["version"]))
+
+ system.ignore = ser["ignore"]
+
+ for el in ser["elements"]:
+ pol = phys.Polarizer(str(el["type"]))
+ pol.angle = float(el["angle"])
+ pol.delta = float(el["delta"])
+ pol.ref = el["ref"]
+ system.elements.append(pol)
+
+ return system \ No newline at end of file
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..df90be5
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,19 @@
+import re, sys, traceback
+from PyQt5.QtWidgets import *
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+
+import ui, phys, io
+
+system = phys.System()
+
+class Window(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.statusBar()
+ self.setWindowTitle("Polarizzazione italiana")
+ ui.setup(self, system)
+
+app = QApplication(sys.argv)
+win = Window()
+sys.exit(app.exec_())
diff --git a/src/phys.py b/src/phys.py
new file mode 100644
index 0000000..2109f28
--- /dev/null
+++ b/src/phys.py
@@ -0,0 +1,99 @@
+import re, sys, traceback
+import numpy as np
+import scipy.optimize
+from PyQt5.QtWidgets import *
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+
+from ui import *
+
+def R(theta):
+ return np.array([[np.cos(theta), np.sin(theta)],
+ [-np.sin(theta), np.cos(theta)]])
+
+
+class Ellipse:
+ def __init__(self, state):
+ # FIXME: a less brute-force way of doing this
+ if state is None:
+ self.alpha = np.nan
+ self.theta = np.nan
+ self.e = np.nan
+ self.a = np.nan
+ self.b = np.nan
+ return
+
+ def x(theta):
+ return np.real(np.exp(1j * theta) * state)
+
+ def r(theta):
+ return np.linalg.norm(x(theta))
+
+ def angle(x):
+ a = np.arctan2(x[1], x[0])
+ if a < 0:
+ a += 2 * np.pi
+ if a > np.pi:
+ a -= np.pi
+ return a
+
+ opt = scipy.optimize.minimize_scalar(r, bounds=[0, np.pi], \
+ method="bounded")
+ self.b = r(opt.x)
+ opt = scipy.optimize.minimize_scalar(lambda x: -r(x), \
+ bounds=[0, np.pi], method="bounded")
+ self.a = r(opt.x)
+
+ self.alpha = angle(x(opt.x))
+
+ self.e = self.b / self.a
+ self.theta = np.arctan(self.e)
+ if self.alpha > angle(x(opt.x + 0.001)):
+ self.theta *= -1
+
+class Polarizer:
+ def __init__(self, type, delta=0):
+ self.type = type
+ self.angle = 0
+ self.delta = delta
+ self.ref = False # FIXME: move this to UI or System
+ self.set_type(type)
+
+ def set_type(self, type):
+ if type == "linear":
+ self.M = np.array([[1, 0], [0, 0]])
+ elif type == "quarterwave":
+ self.M = np.exp(-1j / 4 * np.pi) * \
+ np.array([[1, 0], [0, 1j]])
+ else:
+ raise ValueError("bad Polarizer type: %s" % type)
+ self.type = type
+
+ def mul(self, state):
+ # unpolarized light
+ if state is None:
+ if self.type == "linear":
+ return np.dot(R(-self.angle - self.delta), \
+ np.array([[1], [0]]))
+ else:
+ return None
+
+ M = np.matmul(R(-self.angle - self.delta), \
+ np.matmul(self.M, R(self.angle + self.delta)))
+ return np.dot(M, state)
+
+class System:
+ def __init__(self):
+ self.elements = list()
+ self.ignore = list()
+
+ def recalculate(system):
+ system.states = [None] * len(system.elements)
+ system.ellipses = list()
+
+ state = None
+ for i, pol in enumerate(system.elements):
+ if i >= len(system.ignore) or not system.ignore[i]:
+ state = pol.mul(state)
+ system.states[i] = state
+ system.ellipses.append(Ellipse(state))
diff --git a/src/ui.py b/src/ui.py
new file mode 100644
index 0000000..7803d3d
--- /dev/null
+++ b/src/ui.py
@@ -0,0 +1,421 @@
+import numpy as np, scipy.optimize
+import traceback
+from PyQt5.QtWidgets import *
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+
+import phys, file
+
+# global GUI-related shit
+class GUI:
+ table = None
+ table_rows = list()
+ monospace = QFont("monospace")
+ bigfont = QFont("sans-serif", pointSize=10, weight=1000)
+
+class Pens:
+ axes = QPen(Qt.gray)
+
+ ellipse = QPen(Qt.black)
+ ellipse.setWidth(2)
+
+ axis_linear = QPen(QColor(201, 141, 0))
+ axis_linear.setWidth(2)
+ axis_linear.setStyle(Qt.DashDotLine)
+
+ axis_fast = QPen(QColor(51, 87, 123))
+ axis_fast.setWidth(2)
+ axis_fast.setStyle(Qt.DashDotLine)
+
+ axis_slow = QPen(QColor(55, 123, 51))
+ axis_slow.setWidth(2)
+ axis_slow.setStyle(Qt.DashDotLine)
+
+ alpha = QPen(Qt.red)
+ theta = QPen(Qt.blue)
+
+ radii = QPen(Qt.black)
+ radii.setStyle(Qt.DashDotLine)
+
+from ui_widgets import *
+
+
+
+class OptBox(QVBoxLayout):
+ def __init__(self, row, pol, rownum):
+ super().__init__()
+
+ # Name
+ self.name = MeinGroßLabel("Element %d" % rownum)
+ self.addWidget(self.name)
+
+ # Type / Enable box
+ self.hbox = QHBoxLayout()
+ self.addLayout(self.hbox)
+
+ # Type combo
+ self.type = QComboBox()
+ self.type.addItem("Linear", "linear",)
+ self.type.addItem("Quarterwave plate", "quarterwave")
+ self.type.setCurrentIndex(0 if pol.type == "linear" else 1)
+ self.type.currentIndexChanged.connect(lambda : TableRow.type_change(row))
+ self.hbox.addWidget(self.type)
+
+ # Enable checkbox
+ self.enable = QCheckBox("Enable")
+ self.enable.setChecked(True)
+ self.enable.stateChanged.connect(update)
+ self.hbox.addWidget(self.enable)
+
+ # Delta angle
+ self.delta = AngleSlider("Delta")
+ self.delta.setValue(pol.delta * 180 / np.pi)
+ self.delta.on_change = lambda: TableRow.angle_change(row)
+ self.addLayout(self.delta)
+
+ # Angle reference
+ self.ref = QComboBox()
+ self.ref.addItem("Absolute", False)
+ for i, _ in enumerate(system.elements):
+ if i >= rownum - 1:
+ continue
+ self.ref.addItem("Relative to element #%d" % (i + 1), i)
+
+ if row.ref is not False and i == row.ref:
+ self.ref.setCurrentIndex(row.ref + 1)
+
+ self.ref.currentIndexChanged.connect(update)
+ self.addWidget(self.ref)
+
+ # Angle
+ self.angle = AngleSlider("Angle")
+ self.angle.setValue(pol.angle * 180 / np.pi)
+ self.angle.on_change = lambda: TableRow.angle_change(row)
+ self.addLayout(self.angle)
+ self.addItem(ExpandingSpacer())
+
+ # Add
+ self.add_above = QPushButton("Add above")
+ self.add_above.clicked.connect(lambda: half_assed_element_creation(rownum - 1))
+ self.add_below = QPushButton("Add below")
+ self.add_below.clicked.connect(lambda: half_assed_element_creation(rownum))
+ self.delete = QPushButton("Delete")
+ self.delete.clicked.connect(lambda: half_assed_element_deletion(rownum - 1))
+
+ self.buttons = QHBoxLayout()
+ self.buttons.addWidget(self.add_above)
+ self.buttons.addWidget(self.add_below)
+ self.buttons.addWidget(self.delete)
+ self.addLayout(self.buttons)
+
+
+
+class TableRow:
+ def __init__(self, pol, grid, rownum):
+ self.pol = pol
+ self.grid = grid
+ self.rownum = rownum
+ self.angle = pol.angle
+ self.delta = pol.delta
+ self.ref = pol.ref
+
+ self.optbox = OptBox(self, pol, rownum)
+ grid.addLayout(self.optbox, rownum, 1)
+
+ self.ellipse = EllipseWidget(pol)
+ grid.addWidget(self.ellipse, rownum, 2)
+
+ self.info = QVBoxLayout()
+ self.info_angle = MeinLabel()
+ self.info_angle.setAlignment(Qt.AlignLeft)
+ self.info.addWidget(self.info_angle)
+ self.info.addWidget(QLabel("Polarization state"))
+ self.info_jones = MeinLabel()
+ self.info_jones.setAlignment(Qt.AlignLeft)
+ self.info.addWidget(self.info_jones)
+ self.info.addItem(ExpandingSpacer())
+ grid.addLayout(self.info, rownum, 3)
+
+
+ @staticmethod
+ def angle_change(row):
+ row.angle = row.optbox.angle.angle / 180 * np.pi
+ row.delta = row.optbox.delta.angle / 180 * np.pi
+ update()
+
+ @staticmethod
+ def type_change(row):
+ row.pol.set_type(row.optbox.type.currentData())
+ update()
+
+
+def populate_table():
+ GUI.table = QGridLayout()
+ GUI.table.setColumnStretch(1, 1)
+ GUI.table.setColumnStretch(2, 2)
+ GUI.table.setColumnStretch(3, 5)
+
+ while GUI.opt_operand.count() > 0: # FIXME
+ GUI.opt_operand.removeItem(0)
+
+ GUI.table_rows = list()
+ for i, pol in enumerate(system.elements):
+ GUI.table_rows.append(TableRow(pol, GUI.table, i + 1))
+ GUI.opt_operand.addItem("Element #%d" % (i + 1), i)
+
+def update():
+ system.ignore = [False] * len(system.elements)
+ for i, pol in enumerate(system.elements):
+ row = GUI.table_rows[i]
+
+ pol.angle = row.angle
+ pol.delta = row.delta
+ pol.ref = row.optbox.ref.currentData()
+ if pol.ref is not False:
+ pol.angle += system.elements[pol.ref].angle
+
+ if not row.optbox.enable.isChecked():
+ system.ignore[i] = True
+
+ system.recalculate()
+
+ I = 1
+ for i, pol in enumerate(system.elements):
+ row = GUI.table_rows[i]
+
+ # update all the diagrams
+ row.ellipse.state = system.states[i]
+ row.ellipse.ellipse = system.ellipses[i]
+ row.ellipse.is_used = not system.ignore[i]
+ row.ellipse.repaint()
+
+ text = "%s (%f°)\nat %f°" \
+ % (pol.type, pol.delta * 180 / np.pi,
+ (pol.angle + pol.delta) * 180 / np.pi)
+ row.info_angle.setText(text)
+
+ state = system.states[i]
+ ellipse = system.ellipses[i]
+
+ if state is None:
+ I = 1
+ row.info_jones.setText("Unpolarized")
+ else:
+ I = np.abs(state[0] * state[0].conjugate() \
+ + state[1] * state[1].conjugate())
+
+ if state is None:
+ continue
+
+ A, B = state
+ text = "I = %f\n" % I
+ text += "%f ∠ %+f°\n%f ∠ %+f°\n" % \
+ (np.abs(A), 180 / np.pi * np.angle(A), \
+ np.abs(B), 180 / np.pi * np.angle(B))
+ text += "α = %f°\nφ = %+f°" % \
+ (180 / np.pi * ellipse.alpha, 180 / np.pi * ellipse.theta)
+ text = text.replace("-", "- ").replace("+", "+ ")
+ row.info_jones.setText(text)
+
+ GUI.widok.intensity = I
+ GUI.widok.repaint()
+
+ #if GUI.auto_optimize.isChecked():
+ # optimize()
+
+
+#TODO later
+#class AddElementWindow(QMainWindow):
+# dialog = None
+#
+# def __init__(self):
+# super().__init__()
+#
+# chuj = QLabel("adolf")
+# self.setCentralWidget(chuj)
+# self.show()
+#
+#
+# def __del__(self):
+# AddElementWindow.dialog = None
+#
+# def open():
+# if not AddElementWindow.dialog:
+# AddElementWindow.dialog = AddElementWindow()
+def half_assed_element_creation(index=None):
+ if index is None:
+ system.elements.append(phys.Polarizer("linear"))
+ else:
+ system.elements.insert(index, phys.Polarizer("linear"))
+
+ populate_table()
+ GUI.table_frame = QFrame()
+ GUI.table_frame.setLayout(GUI.table)
+ GUI.scroll.setWidget(GUI.table_frame)
+ update()
+
+def half_assed_clear():
+ system.elements = list()
+ populate_table()
+ GUI.table_frame = QFrame()
+ GUI.table_frame.setLayout(GUI.table)
+ GUI.scroll.setWidget(GUI.table_frame)
+ update()
+
+def half_assed_element_deletion(index):
+ del(system.elements[index])
+
+ populate_table()
+ GUI.table_frame = QFrame()
+ GUI.table_frame.setLayout(GUI.table)
+ GUI.scroll.setWidget(GUI.table_frame)
+ update()
+
+
+last_save_path = None
+file_filter = "Wery Omportant Zejta (*.woz)"
+
+def do_save(win, reuse_old):
+ global system, last_save_path
+
+ if reuse_old and last_save_path:
+ path = last_save_path
+ else:
+ path, _ = QFileDialog.getSaveFileName(win, filter=file_filter)
+ if path == "":
+ return
+
+ try:
+ file.save_system(path, system)
+ except:
+ traceback.print_exc()
+
+ last_save_path = path
+
+def do_open(win):
+ global system
+
+ path, _ = QFileDialog.getOpenFileName(win, filter=file_filter)
+ if path == "":
+ return
+
+ try:
+ system = file.open_system(path)
+ except:
+ traceback.print_exc()
+
+ populate_table()
+ GUI.table_frame = QFrame()
+ GUI.table_frame.setLayout(GUI.table)
+ GUI.scroll.setWidget(GUI.table_frame)
+ update()
+
+def setup_menubar(win):
+ menu = win.menuBar()
+
+ # File
+ open = QAction("&Open a system", win)
+ open.setShortcut("Ctrl+O")
+ open.triggered.connect(lambda: do_open(win))
+ save = QAction("&Save a system", win)
+ save.setShortcut("Ctrl+S")
+ save.triggered.connect(lambda: do_save(win, True))
+ save_as = QAction("&Save a system as...", win)
+ save_as.triggered.connect(lambda: do_save(win, False))
+ close = QAction("Exit", win)
+ close.setShortcut("Ctrl+Q")
+ close.triggered.connect(exit)
+
+ file = menu.addMenu("&File")
+ file.addAction(open)
+ file.addAction(save)
+ file.addAction(save_as)
+ file.addAction(close)
+
+ # System
+ add = QAction("&Add a new element", win)
+ add.setShortcut("Ctrl+N")
+ add.triggered.connect(half_assed_element_creation)
+ clear = QAction("&Remove all elements", win)
+ clear.triggered.connect(half_assed_clear)
+
+ system = menu.addMenu("&System")
+ system.addAction(add)
+ system.addAction(clear)
+
+ win.statusBar()
+
+# FIXME: refactor
+def optimize(which):
+ if len(system.elements) == 0:
+ return
+
+ op_idx = GUI.opt_operand.currentData()
+ op = system.elements[op_idx]
+
+ def I(theta):
+ op.angle = theta
+ if op.ref is not False:
+ op.ref += system.elements[op.ref].angle
+
+ state = None
+ for i, pol in enumerate(system.elements):
+ if i >= len(system.ignore) or not system.ignore[i]:
+ state = pol.mul(state)
+ if state is None:
+ return 1
+ else:
+ return float(np.abs(state[0] * state[0].conjugate() \
+ + state[1] * state[1].conjugate()))
+
+ if which == "min":
+ f = I
+ else:
+ f = lambda theta: -I(theta)
+
+ opt = scipy.optimize.minimize_scalar(f, bounds=[0, np.pi], method="bounded")
+ GUI.table_rows[op_idx].optbox.angle.edit.setText("%g" % round(opt.x * 180 / np.pi, 3))
+
+
+def setup(win, system_):
+ global system
+ system = system_
+
+ # Needless to say, I'm getting real tired of this whole Layout/Widget
+ # clusterfuck in Qt
+ root = QHBoxLayout()
+ root_fucking_random_container = QWidget()
+ root_fucking_random_container.setLayout(root)
+ win.setCentralWidget(root_fucking_random_container)
+
+ rhs = QVBoxLayout()
+
+ Widocques.image = QImage("/home/enneract/lab/test/c.jpg")
+ GUI.widok = Widocques()
+ rhs.addWidget(GUI.widok)
+
+ optbox = QHBoxLayout()
+ rhs.addLayout(optbox)
+
+ GUI.opt_operand = QComboBox()
+ optbox.addWidget(GUI.opt_operand)
+ button = QPushButton("Find a minimum")
+ button.pressed.connect(lambda: optimize("min"))
+ optbox.addWidget(button)
+ button = QPushButton("Find a maximum")
+ button.pressed.connect(lambda: optimize("max"))
+ optbox.addWidget(button)
+
+ GUI.scroll = QScrollArea()
+ populate_table()
+ GUI.table_frame = QFrame()
+ GUI.table_frame.setLayout(GUI.table)
+ GUI.scroll.setWidget(GUI.table_frame)
+
+ root.addWidget(GUI.scroll)
+ root.addLayout(rhs)
+
+ setup_menubar(win)
+
+ update()
+ win.show() \ No newline at end of file
diff --git a/src/ui_widgets.py b/src/ui_widgets.py
new file mode 100644
index 0000000..48282c4
--- /dev/null
+++ b/src/ui_widgets.py
@@ -0,0 +1,226 @@
+import numpy as np
+from PyQt5.QtWidgets import *
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+
+from ui import GUI, Pens # the fuck
+
+class ExpandingSpacer(QSpacerItem):
+ def __init__(self):
+ super().__init__(0, 0, QSizePolicy.Minimum, \
+ QSizePolicy.Expanding)
+
+class MeinLabel(QLabel):
+ def __init__(self, *args):
+ QLabel.__init__(self, *args)
+ self.setFont(GUI.monospace)
+
+class MeinGroßLabel(QLabel):
+ def __init__(self, *args):
+ QLabel.__init__(self, *args)
+ self.setFont(GUI.bigfont)
+ self.setAlignment(Qt.AlignHCenter)
+
+class EllipseWidget(QWidget):
+ def __init__(self, pol):
+ QWidget.__init__(self)
+ self.pol = pol
+ self.state = None
+ self.ellipse = None
+ self.is_used = True
+
+ def minimumSizeHint(self):
+ return QSize(150, 150)
+
+ def paintEvent(self, event):
+ P = QPainter(self)
+
+ w, h = self.frameSize().width(), self.frameSize().height()
+ cx, cy = w / 2, h / 2
+ r = min(w, h) / 2
+
+ # background
+ if self.is_used:
+ background = Qt.white
+ else:
+ background = QColor(0, 0, 0, 100)
+ P.fillRect(QRect(0, 0, w, h), background)
+
+ # coordinate axes
+ P.setPen(Pens.axes)
+ P.drawLine(cx - 20 * r, cy, cx + 20 * r, cy)
+ P.drawLine(cx, cy - 20 * r, cx, cy + 20 * r)
+
+ # polarizer axes
+ P.translate(cx, cy)
+ P.rotate(- (self.pol.angle + self.pol.delta) * 180 / np.pi)
+ if self.pol.type == "linear":
+ P.setPen(Pens.axis_linear)
+ P.drawLine(-20 * r, 0, 20 * r, 0)
+ else:
+ P.setPen(Pens.axis_fast)
+ P.drawLine(-20 * r, 0, 20 * r, 0)
+ P.drawText(0.92 * r, 0, "F")
+ P.setPen(Pens.axis_slow)
+ P.drawLine(0, -20 * r, 0, 20 * r)
+ P.drawText(0, -0.92 * r, "S")
+
+ if self.state is None:
+ return
+
+ P.resetTransform()
+ r *= 0.88
+ P.translate(cx, cy)
+ P.scale(1, -1)
+
+ # radii
+ P.setPen(Pens.radii)
+ csa = np.array([np.cos(self.ellipse.alpha), np.sin(self.ellipse.alpha)])
+ xa = self.ellipse.a * r * csa
+ P.drawLine(-xa[0], -xa[1], xa[0], xa[1])
+ csb = np.array([np.cos(self.ellipse.alpha + np.pi / 2), \
+ np.sin(self.ellipse.alpha + np.pi / 2)])
+ xb = self.ellipse.b * r * csb
+ P.drawLine(-xb[0], -xb[1], xb[0], xb[1])
+
+ def arc(x, y, r, t1, t2):
+ P.drawArc(x - r / 2, y - r / 2, r, r, \
+ -180 * 16 / np.pi * t1, \
+ -180 * 16 / np.pi * t2)
+
+ # azimuth
+ P.setPen(Pens.alpha)
+ P.drawLine(0, 0, xa[0], xa[1])
+ arc(0, 0, self.ellipse.a * r, 0, self.ellipse.alpha)
+
+ # ellipticity
+ P.setPen(Pens.theta)
+ P.drawLine(-xa[0], -xa[1], xb[0], xb[1])
+ arc(-xa[0], -xa[1], r / 2, self.ellipse.alpha, abs(self.ellipse.theta))
+
+ # the ellipse
+ P.setPen(Pens.ellipse)
+ path = QPainterPath()
+ for i, t in enumerate(np.linspace(0, 2 * np.pi, 100)):
+ x = r * np.real(np.exp(1j * t) * self.state)
+ f = path.moveTo if t == 0 else path.lineTo # FML
+ f(x[0], x[1]) # +Y should be upwards
+
+ if abs(self.ellipse.theta) > 0.05 and i % 25 == 0:
+ N = r * np.real(np.exp(1j * (t + 0.001)) * self.state) - x
+ N /= np.linalg.norm(N)
+ if np.any(np.isnan(N)):
+ continue
+
+ T = np.dot(np.array([[0, 1], [-1, 0]]), N)
+
+ ax = x - N * 5 + T * 5
+ P.drawLine(x[0], x[1], ax[0], ax[1])
+ ax = x - N * 5 - T * 5
+ P.drawLine(x[0], x[1], ax[0], ax[1])
+ path.closeSubpath()
+ P.drawPath(path)
+
+ if abs(self.ellipse.theta) <= 0.05:
+ x1 = xa - r / 20 * csb
+ x2 = xa + r / 20 * csb
+ P.drawLine(x1[0], x1[1], x2[0], x2[1])
+ x1 = -xa - r / 20 * csb
+ x2 = -xa + r / 20 * csb
+ P.drawLine(x1[0], x1[1], x2[0], x2[1])
+
+
+
+class AngleSlider(QHBoxLayout):
+ def __init__(self, label=None):
+ super().__init__()
+ self.angle = 0
+
+ if label:
+ self.addWidget(QLabel(label))
+
+ self.slider = QSlider(Qt.Horizontal)
+ self.slider.setMinimumWidth(100)
+ self.slider.setMinimum(0)
+ self.slider.setMaximum(180)
+ self.slider.setTickPosition(QSlider.TicksBelow)
+ self.slider.setTickInterval(45)
+ self.slider.valueChanged.connect(\
+ lambda: AngleSlider.change(self, "bar"))
+ self.addWidget(self.slider)
+
+ self.edit = QLineEdit()
+ self.edit.setText("0")
+ self.edit.setMinimumWidth(30)
+ self.edit.textChanged.connect(\
+ lambda: AngleSlider.change(self, "edit"))
+ self.addWidget(self.edit)
+ self.addWidget(QLabel("°"))
+
+ def setValue(self, angle):
+ self.angle = angle
+ self.slider.blockSignals(True)
+ self.slider.setValue(angle)
+ self.slider.blockSignals(False)
+ self.edit.blockSignals(True)
+ self.edit.setText("%g" % angle)
+ self.edit.blockSignals(False)
+
+ @staticmethod
+ def change(self, source):
+ if source == "edit":
+ try:
+ angle = float(self.edit.text())
+ except ValueError:
+ return
+ self.slider.blockSignals(True)
+ self.slider.setValue(angle)
+ self.slider.blockSignals(False)
+ elif source == "bar":
+ angle = self.slider.value()
+ self.edit.blockSignals(True)
+ self.edit.setText("%g" % angle)
+ self.edit.blockSignals(False)
+ self.angle = angle
+ self.on_change()
+
+
+
+class Widocques(QWidget):
+ image = None
+
+ def __init__(self):
+ QWidget.__init__(self)
+ self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
+ self.intensity = 1
+
+ def minimumSizeHint(self):
+ return QSize(100, 100)
+
+ def paintEvent(self, event):
+ P = QPainter(self)
+
+ w, h = self.frameSize().width(), self.frameSize().height()
+
+ ar = Widocques.image.width() \
+ / Widocques.image.height()
+
+ if w / h > ar:
+ # pad left/right
+ w2 = h * ar
+ pad = (w - w2) / 2
+ rect = QRect(pad, 0, w2, h)
+ else:
+ # pad top/bottom
+ h2 = w / ar
+ pad = (h - h2) / 2
+ rect = QRect(0, pad, w, h2)
+
+ w2 = rect.width()
+ h2 = rect.height()
+ rect2 = QRect(rect.left() + 0.1 * w2, rect.top() + 0.1 * h2, \
+ w2 * 0.8, h2 * 0.8)
+
+ P.fillRect(QRect(0, 0, w, h), Qt.black)
+ P.drawImage(rect2, Widocques.image)
+ P.fillRect(rect2, QColor(0, 0, 0, 255 * (1 - self.intensity)))