Skip to content

Pane Control Content Grid

pane_control_content_grid

This is part of the janus package.

Attributes

__author__ module-attribute

__author__ = 'Jan Meyer'
__copyright__ = '(c)2024 DESY, FS-BMX'

__email__ module-attribute

__email__ = 'jan.meyer@desy.de'

__license__ module-attribute

__license__ = 'GPL'

Classes

Grid

Grid(chip: Chip)

Bases: QObject

Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def __init__(self, chip: Chip) -> None:
    QObject.__init__(self)
    self.transform = QTransform.fromTranslate(0, 0)
    self.chip = None
    self.angle = 0.0
    self._window_count = QPoint(0, 0)
    self._window_borders = []
    self.set_angle(0)
    self.set_chip(chip)
Attributes
angle instance-attribute
angle = 0.0
chip instance-attribute
chip = None
transform instance-attribute
transform = fromTranslate(0, 0)
value_changed class-attribute instance-attribute
value_changed = pyqtSignal(name='valueChanged')
Functions
get_angle
get_angle() -> float
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_angle(self) -> float:
    return self.angle
get_bounding_box
get_bounding_box() -> List[QPointF]
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_bounding_box(self) -> List[QPointF]:
    return [self.transform.map(c) for c in self.bounding_box]
get_chip_border
get_chip_border() -> QPolygonF
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_chip_border(self) -> QPolygonF:
    if self.chip is None:
        return self._transform_rect(QRectF(0.0, 0.0, 0.0, 0.0))
    return self._transform_rect(self._chip_border)
get_origin_offset
get_origin_offset() -> Optional[QPointF]
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_origin_offset(self) -> Optional[QPointF]:
    if self.chip is None:
        return None
    return self.chip.origin_offset
get_points
get_points(invalid: bool = True, ignored: bool = True, window: int = None, rect: QRectF = None) -> NDArray
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_points(self, invalid: bool = True, ignored: bool = True,
        window: int = None, rect: QRectF = None) -> NDArray:
    if self.chip is None:
        return array([])
    points = self.points
    invalid_mask = self.invalid
    ignored_mask = self.margin
    conv_x = self.chip.chip_size.x() / points.shape[1]
    conv_y = self.chip.chip_size.y() / points.shape[0]
    angle = radians(self.angle)
    rot = array([[cos(angle), sin(angle)], [-sin(angle), cos(angle)]])
    # filter by given window number
    if window is not None:
        if window < len(self._window_borders):
            window_border = self._window_borders[window]
            start_x = max(0, floor(window_border.left() / conv_x))
            end_x = min(ceil(window_border.right() / conv_x), points.shape[1])
            start_y = max(0, floor(window_border.top() / conv_y))
            end_y = min(ceil(window_border.bottom() / conv_y), points.shape[0])
            points = points[start_y:end_y, start_x:end_x, :]
            invalid_mask = invalid_mask[start_y:end_y, start_x:end_x]
            ignored_mask = ignored_mask[start_y:end_y, start_x:end_x]
        else:
            return array([])
    # raw filter by given rectangle
    if rect is not None:
        c = rect.center()
        r = QVector2D(c - rect.topLeft()).length()
        c -= QPointF(self.chip.origin_offset.x(), self.chip.origin_offset.y())
        c = QPointF(c.x() * cos(angle) - c.y() * sin(angle), c.x() * sin(angle) + c.y() * cos(angle))
        r = ceil((max(r / conv_x, r / conv_y)))
        start_x = max(0, int(c.x() / conv_x) - r)
        end_x = max(0, min(points.shape[1], int(c.x() / conv_x) + r + 1))
        start_y = max(0, int(c.y() / conv_y) - r)
        end_y = max(0, min(points.shape[0], int(c.y() / conv_y) + r + 1))
        if start_x == 0 and start_y == 0 and end_x == points.shape[1] and end_y == points.shape[0]:
            rect = None
        else:
            points = points[start_y:end_y, start_x:end_x, :]
            invalid_mask = invalid_mask[start_y:end_y, start_x:end_x]
            ignored_mask = ignored_mask[start_y:end_y, start_x:end_x]
    # rotate and shift point coordinates
    points = points.dot(rot.T)
    points = points[:,:] + (self.chip.origin_offset.x(), self.chip.origin_offset.y())
    # create fine filter mask for given rectangle
    if rect is not None:
        rect_mask = ((points[:,:,0] > rect.left()) & (points[:,:,0] < rect.right()) &
                (points[:,:,1] > rect.top()) & (points[:,:,1] < rect.bottom()))
    # find lines with points
    lines = arange(points.shape[0])
    if not invalid:
        lines = intersect1d(unique(where(invalid_mask == False)[0]), lines)
    if not ignored:
        lines = intersect1d(unique(where(ignored_mask == False)[0]), lines)
    if rect is not None:
        lines = intersect1d(unique(where(rect_mask)[0]), lines)
    # apply masks
    rows = empty((lines.size,), dtype=ndarray)
    r = 0
    if not ignored and not invalid and rect is not None:
        for l in lines:
            rows[r] = points[l, ((ignored_mask[l] | invalid_mask[l]) == False) & rect_mask[l]]
            r += 1
    elif not ignored and not invalid:
        for l in lines:
            rows[r] = points[l, (ignored_mask[l] | invalid_mask[l]) == False]
            r += 1
    elif not invalid and rect is not None:
        for l in lines:
            rows[r] = points[l, (invalid_mask == False) & rect_mask[l]]
            r += 1
    elif not invalid:
        for l in lines:
            rows[r] = points[l, invalid_mask[l] == False]
            r += 1
    elif not ignored and rect is not None:
        for l in lines:
            rows[r] = points[l, (ignored_mask[l] == False) & rect_mask[l]]
            r += 1
    elif not ignored:
        for l in lines:
            rows[r] = points[l, ignored_mask[l] == False]
            r += 1
    elif rect is not None:
        for l in lines:
            rows[r] = points[l, rect_mask[l]]
            r += 1
    return rows
get_row_info
get_row_info(window: int = None) -> List[GridRowInfo]
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_row_info(self, window: int = None) -> List[GridRowInfo]:
    row_info = []
    unit_vector = QPointF(cos(radians(self.angle)), sin(radians(self.angle)))
    if window is None:
        for info in self.row_info:
            row_info.append(GridRowInfo(
                start_pos=[self.transform.map(info.start_pos[0])],
                end_pos=[self.transform.map(info.end_pos[-1])],
                unit_vector=unit_vector,
                point_distance=info.point_distance,
                window_points=info.window_points,
                support_points=info.support_points
            ))
    elif window < self._window_count.x() * self._window_count.y():
        window_x = window % self._window_count.x()
        window_y = floor(window / self._window_count.x())
        row_start = self._window_indices[window_y]
        row_end = len(self.row_info) if window_y == self._window_count.y() - 1 else self._window_indices[window_y + 1]
        for info in self.row_info[row_start:row_end]:
            row_info.append(GridRowInfo(
                start_pos=[self.transform.map(info.start_pos[window_x])],
                end_pos=[self.transform.map(info.end_pos[window_x])],
                unit_vector=unit_vector,
                point_distance=info.point_distance,
                window_points=[info.window_points[window_x]],
                support_points=[]
            ))
    return row_info
get_top_left_offset
get_top_left_offset() -> QPointF
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_top_left_offset(self) -> QPointF:
    chip_corner = self.transform.map(QPointF())
    points_corner = self.transform.map(self.bounding_box[0])
    return points_corner - chip_corner
get_tranform
get_tranform() -> QTransform
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_tranform(self) -> QTransform:
    return self.transform
get_window_borders
get_window_borders() -> List[QPolygonF]
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_window_borders(self) -> List[QPolygonF]:
    if self.chip is None:
        return []
    polygons = []
    for rect in self._window_borders:
        polygons.append(self._transform_rect(rect))
    return polygons
rotate
rotate(value: float, center: Optional[QPointF] = None) -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def rotate(self, value: float, center: Optional[QPointF] = None) -> None:
    if center is None:
        center = QPointF(self.get_chip_border().boundingRect().center())
    angle = -self.get_angle()
    inverted, _ = self.transform.inverted()
    center_chip_coords = inverted.map(center)
    offset = center - QTransform().rotate(-(angle + value)).map(center_chip_coords)
    self.chip.origin_offset = offset
    self.angle = angle + value
    self._set_transform(offset.x(), offset.y(), angle + value)
set_angle
set_angle(value: float) -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def set_angle(self, value: float) -> None:
    self.angle = value
    x = self.transform.m31()
    y = self.transform.m32()
    self._set_transform(x, y, self.angle)
set_chip
set_chip(chip: Chip) -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def set_chip(self, chip: Chip) -> None:
    self.chip = chip
    if chip is None:
        return
    self.set_origin_offset(self.chip.origin_offset)
    self._chip_border = QRectF(0.0, 0.0, self.chip.chip_size.x(),
            self.chip.chip_size.y())
    # calculate window lines
    n_windows_x = 1
    while (n_windows_x * self.chip.window_size.x() +
            (n_windows_x - 1 ) * self.chip.support_size.x()) \
            < self.chip.chip_size.x() + 0.000001:
        n_windows_x += 1
    n_windows_x -= 1
    offset_windows_x = (self.chip.chip_size.x() -
            (n_windows_x * self.chip.window_size.x() +
            (n_windows_x - 1) * self.chip.support_size.x())) / 2.0
    # calculate window columns
    n_windows_y = 1
    while (n_windows_y * self.chip.window_size.y() +
            (n_windows_y - 1 ) * self.chip.support_size.y()) \
            < self.chip.chip_size.y() + 0.000001:
        n_windows_y += 1
    n_windows_y -= 1
    offset_windows_y = (self.chip.chip_size.y() -
            (n_windows_y * self.chip.window_size.y() +
            (n_windows_y - 1) * self.chip.support_size.y())) / 2.0
    size = QSizeF(self.chip.window_size.x(), self.chip.window_size.y())
    # build window list
    self._window_borders = []
    self._window_count = QPoint(n_windows_x, n_windows_y)
    for y in range(n_windows_y):
        for x in range(n_windows_x):
            self._window_borders.append(QRectF(QPointF(
                offset_windows_x + x * (self.chip.window_size.x() +
                self.chip.support_size.x()),
                offset_windows_y + y * (self.chip.window_size.y() +
                self.chip.support_size.y())),
                size))
    # generate points
    points = mgrid[
            0:self.chip.chip_size.x() + 0.000001:self.chip.hole_distance.x(),
            0:self.chip.chip_size.y() + 0.000001:self.chip.hole_distance.y()]
    if chip.odd_indentation != 0.0:
        points[0][:, 1::2] += chip.odd_indentation
    # mark support structures and safety margins
    invalid = zeros_like(points[0], dtype=bool)
    margin = zeros_like(points[0], dtype=bool)
    # find invalid rows
    invalid_end_pos = ceil(offset_windows_y / self.chip.hole_distance.y())
    margin_end_pos = ceil((offset_windows_y + self.chip.safety_margin.y()) /
            self.chip.hole_distance.y())
    invalid_rows = list(range(invalid_end_pos))
    margin_rows = list(range(invalid_end_pos, margin_end_pos))
    first_row = margin_end_pos
    window_start_indices = [margin_end_pos]
    for y in range(1, n_windows_y):
        margin_start_pos = ceil((offset_windows_y + y * self.chip.window_size.y() +
                (y - 1) * self.chip.support_size.y() - self.chip.safety_margin.y()) /
                self.chip.hole_distance.y())
        invalid_start_pos = ceil((offset_windows_y + y * self.chip.window_size.y() +
                (y - 1) * self.chip.support_size.y()) / self.chip.hole_distance.y())
        invalid_end_pos = ceil((offset_windows_y + y * self.chip.window_size.y() +
                y * self.chip.support_size.y()) / self.chip.hole_distance.y())
        margin_end_pos = ceil((offset_windows_y + y * self.chip.window_size.y() +
                y * self.chip.support_size.y() + self.chip.safety_margin.y()) /
                self.chip.hole_distance.y())
        invalid_rows += list(range(invalid_start_pos, invalid_end_pos))
        margin_rows += list(range(margin_start_pos, invalid_start_pos))
        margin_rows += list(range(invalid_end_pos, margin_end_pos))
        window_start_indices += [margin_end_pos]
    invalid_start_pos = points.shape[2] - ceil(offset_windows_y /
            self.chip.hole_distance.y())
    margin_start_pos = points.shape[2] - ceil((offset_windows_y +
            self.chip.safety_margin.y()) / self.chip.hole_distance.y())
    invalid_rows += list(range(invalid_start_pos, points.shape[2]))
    margin_rows += list(range(margin_start_pos, invalid_start_pos))
    last_row = margin_start_pos - 1
    invalid[:, invalid_rows] = True
    margin[:, margin_rows] = True
    # find invalid columns
    support_width_even = []
    window_width_even = []
    invalid_end_pos = ceil(offset_windows_x / self.chip.hole_distance.x())
    margin_end_pos = ceil((offset_windows_x + self.chip.safety_margin.x()) /
            self.chip.hole_distance.x())
    invalid_columns = list(range(invalid_end_pos))
    margin_columns = list(range(invalid_end_pos, margin_end_pos))
    first_column_even = margin_end_pos
    for x in range(1, n_windows_x):
        margin_start_pos = ceil((offset_windows_x + x * self.chip.window_size.x() +
                (x - 1) * self.chip.support_size.x() - self.chip.safety_margin.x()) /
                self.chip.hole_distance.x())
        invalid_start_pos = ceil((offset_windows_x + x * self.chip.window_size.x() +
                (x - 1) * self.chip.support_size.x()) / self.chip.hole_distance.x())
        window_width_even += [margin_start_pos - margin_end_pos]
        invalid_end_pos = ceil((offset_windows_x + x * self.chip.window_size.x() +
                x * self.chip.support_size.x()) / self.chip.hole_distance.x())
        margin_end_pos = ceil((offset_windows_x + x * self.chip.window_size.x() +
                x * self.chip.support_size.x() + self.chip.safety_margin.x()) /
                self.chip.hole_distance.x())
        invalid_columns += list(range(invalid_start_pos, invalid_end_pos))
        margin_columns += list(range(margin_start_pos, invalid_start_pos))
        margin_columns += list(range(invalid_end_pos, margin_end_pos))
        support_width_even += [margin_end_pos - margin_start_pos]
    invalid_start_pos = points.shape[1] - ceil(offset_windows_x /
            self.chip.hole_distance.x())
    margin_start_pos = points.shape[1] - ceil((offset_windows_x +
            self.chip.safety_margin.x()) / self.chip.hole_distance.x())
    invalid_columns += list(range(invalid_start_pos, points.shape[1]))
    margin_columns += list(range(margin_start_pos, invalid_start_pos))
    window_width_even += [margin_start_pos - margin_end_pos]
    last_column_even = margin_start_pos - 1
    # and again for the hexagonal patterns
    window_width_odd = []
    support_width_odd = []
    if chip.odd_indentation != 0.0:
        invalid_end_pos = ceil((offset_windows_x - chip.odd_indentation) /
                self.chip.hole_distance.x())
        margin_end_pos = ceil((offset_windows_x + self.chip.safety_margin.x() -
                chip.odd_indentation) / self.chip.hole_distance.x())
        invalid_odd_columns = list(range(invalid_end_pos))
        margin_odd_columns = list(range(invalid_end_pos, margin_end_pos))
        first_column_odd = margin_end_pos
        for x in range(1, n_windows_x):
            margin_start_pos = ceil((offset_windows_x + x * self.chip.window_size.x() +
                    (x - 1) * self.chip.support_size.x() - self.chip.safety_margin.x() -
                    chip.odd_indentation) / self.chip.hole_distance.x())
            invalid_start_pos = ceil((offset_windows_x + x * self.chip.window_size.x() +
                    (x - 1) * self.chip.support_size.x() - chip.odd_indentation) /
                    self.chip.hole_distance.x())
            window_width_odd += [margin_start_pos - margin_end_pos]
            invalid_end_pos = ceil((offset_windows_x + x * self.chip.window_size.x() +
                    x * self.chip.support_size.x() - chip.odd_indentation) /
                    self.chip.hole_distance.x())
            margin_end_pos = ceil((offset_windows_x + x * self.chip.window_size.x() +
                    x * self.chip.support_size.x() + self.chip.safety_margin.x() -
                    chip.odd_indentation) / self.chip.hole_distance.x())
            invalid_odd_columns += list(range(invalid_start_pos, invalid_end_pos))
            margin_odd_columns += list(range(margin_start_pos, invalid_start_pos))
            margin_odd_columns += list(range(invalid_end_pos, margin_end_pos))
            support_width_odd += [margin_end_pos - margin_start_pos]
        invalid_start_pos = points.shape[1] - ceil((offset_windows_x +
                chip.odd_indentation) / self.chip.hole_distance.x())
        margin_start_pos = points.shape[1] - ceil((offset_windows_x +
                chip.odd_indentation + self.chip.safety_margin.x()) /
                self.chip.hole_distance.x())
        invalid_odd_columns += list(range(invalid_start_pos, points.shape[1]))
        margin_odd_columns += list(range(margin_start_pos, invalid_start_pos))
        window_width_odd += [margin_start_pos - margin_end_pos]
        last_column_odd = margin_start_pos - 1
        invalid[invalid_odd_columns, 1::2] = True
        margin[margin_odd_columns, 1::2] = True
        invalid[invalid_columns, ::2] = True
        margin[margin_columns, ::2] = True
    else:
        invalid[invalid_columns, ] = True
        margin[margin_columns, ] = True
        first_column_odd = first_column_even
        last_column_odd = last_column_even
        window_width_odd = window_width_even
        support_width_odd = support_width_even
    # save bounding box of used scan points
    first_column = (first_column_even, first_column_odd)
    last_column = (last_column_even, last_column_odd)
    self.bounding_box = (
        QPointF(points[0, first_column[first_row % 2], first_row],
                points[1, first_column[first_row % 2], first_row]),
        QPointF(points[0, last_column[first_row % 2], first_row],
                points[1, last_column[first_row % 2], first_row]),
        QPointF(points[0, last_column[last_row % 2], last_row],
                points[1, last_column[last_row % 2], last_row]),
        QPointF(points[0, first_column[last_row % 2], last_row],
                points[1, first_column[last_row % 2], last_row])
    )
    # create row info list
    self.row_info = []
    self._window_indices = []
    rows = list(set(range(points.shape[2])) - set(invalid_rows) - set(margin_rows))
    start_columns_even = [first_column_even]
    for i in range(len(support_width_even)):
        start_columns_even.append(start_columns_even[i] + window_width_even[i] + support_width_even[i])
    end_columns_even = [start_columns_even[i] + window_width_even[i] - 1 for i in range(len(start_columns_even))]
    start_columns_odd = [first_column_odd]
    for i in range(len(support_width_odd)):
        start_columns_odd.append(start_columns_even[i] + window_width_odd[i] + support_width_odd[i])
    end_columns_odd = [start_columns_odd[i] + window_width_odd[i] - 1 for i in range(len(start_columns_odd))]
    i = 0
    for row in rows:
        if row in window_start_indices:
            self._window_indices += [i]
        if row % 2:
            start_pos = [QPointF(points[0, column, row], points[1, column, row]) for column in start_columns_odd]
            end_pos = [QPointF(points[0, column, row], points[1, column, row]) for column in end_columns_odd]
        else:
            start_pos = [QPointF(points[0, column, row], points[1, column, row]) for column in start_columns_even]
            end_pos = [QPointF(points[0, column, row], points[1, column, row]) for column in end_columns_even]
        self.row_info.append(GridRowInfo(
            start_pos=start_pos,
            end_pos=end_pos,
            unit_vector=QPointF(),
            point_distance=self.chip.hole_distance.x(),
            window_points=(window_width_odd if row % 2 else window_width_even),
            support_points=(support_width_odd if row % 2 else support_width_even)
        ))
        i += 1
    # compute angles to allow for faster rotation
    map_handle_to_opposite_corner = [2, 3, 0, 1, 2, 3, 0, 1]
    chip_corners = QPolygonF(self._chip_border)
    self.handle_angles = []
    for i in range(4):
        self.handle_angles.append(QLineF(chip_corners[i], chip_corners[map_handle_to_opposite_corner[i]]).angle())
    for i in range(4):
        self.handle_angles.append(QLineF(self.bounding_box[i], chip_corners[map_handle_to_opposite_corner[i]]).angle())
    # done
    self.points = points.T
    self.invalid = invalid.T
    self.margin = margin.T
    self.value_changed.emit()
set_origin_offset
set_origin_offset(x: Union[float, QPointF], y: Optional[float] = None) -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def set_origin_offset(self, x: Union[float, QPointF], y: Optional[float] = None) -> None:
    if isinstance(x, QPointF):
        y = x.y()
        x = x.x()
    if not(isinstance(x, float) and isinstance(y, float)):
        return
    self.chip.origin_offset = QPointF(x, y)
    self._set_transform(x, y, self.angle)

GridRowInfo dataclass

GridRowInfo(start_pos: List[QPointF], end_pos: List[QPointF], unit_vector: QPointF, point_distance: float, window_points: List[int], support_points: List[int])
Attributes
end_pos instance-attribute
end_pos: List[QPointF]
point_distance instance-attribute
point_distance: float
start_pos instance-attribute
start_pos: List[QPointF]
support_points instance-attribute
support_points: List[int]
unit_vector instance-attribute
unit_vector: QPointF
window_points instance-attribute
window_points: List[int]
Functions

PaneControlContentGrid

PaneControlContentGrid(pane: Optional[Pane] = None)

Bases: PaneControlContent

Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def __init__(self, pane: Optional[Pane] = None) -> None:
    QObject.__init__(self)
    self.pane = pane
    self._cursor = None
    self._action = None
    self._handle = None
    self._mouse_pressed_pos = None
    self._placer = PaneControlContentPlaceChip(self)
    self.center = None
    if self.pane is not None and self.pane.toolbar is not None:
        self.setup_ui()
        self.register_persistents()
    self.grid = None
Attributes
ACTION_MOVE class-attribute instance-attribute
ACTION_MOVE = 1
ACTION_RESIZE class-attribute instance-attribute
ACTION_RESIZE = 3
ACTION_ROTATE class-attribute instance-attribute
ACTION_ROTATE = 2
HANDLE_ACTIVE_RADIUS class-attribute instance-attribute
HANDLE_ACTIVE_RADIUS = 5.0
center instance-attribute
center = None
grid instance-attribute
grid = None
pane instance-attribute
pane = pane
value_changed class-attribute instance-attribute
value_changed = pyqtSignal(name='valueChanged')
Classes
HandleType

Bases: IntEnum

Attributes
CHIP_AREA class-attribute instance-attribute
CHIP_AREA = 8
CHIP_BOTTOM_LEFT class-attribute instance-attribute
CHIP_BOTTOM_LEFT = 3
CHIP_BOTTOM_RIGHT class-attribute instance-attribute
CHIP_BOTTOM_RIGHT = 2
CHIP_TOP_LEFT class-attribute instance-attribute
CHIP_TOP_LEFT = 0
CHIP_TOP_RIGHT class-attribute instance-attribute
CHIP_TOP_RIGHT = 1
POINT_BOTTOM_LEFT class-attribute instance-attribute
POINT_BOTTOM_LEFT = 7
POINT_BOTTOM_RIGHT class-attribute instance-attribute
POINT_BOTTOM_RIGHT = 6
POINT_TOP_LEFT class-attribute instance-attribute
POINT_TOP_LEFT = 4
POINT_TOP_RIGHT class-attribute instance-attribute
POINT_TOP_RIGHT = 5
Functions
exchange_grid
exchange_grid(name)
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def exchange_grid(self, name):
    old = self.get_grid()
    if len(old) > 1:
        [old_name, x, y, angle] = old
        self.set_grid([name, x, y, angle])
get_grid
get_grid() -> List
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def get_grid(self) -> List:
    if self.grid is None:
        return []
    name = self.grid.chip.name
    pos = self.grid.get_origin_offset()
    angle = self.grid.get_angle()
    return [name, pos.x(), pos.y(), angle]
input_event
input_event(source: QObject, event: QEvent) -> bool
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def input_event(self, source: QObject, event: QEvent) -> bool:
    if isinstance(event, QMouseEvent) and self.grid is not None:
        handle, corners = self.is_handle(event.pos())
        # change mouse cursor if hovering over the area or one of the corners
        if ((event.type() == QEvent.MouseMove) and
                (event.buttons() == Qt.NoButton)):
            if handle is not None:
                if handle == PaneControlContentGrid.HandleType.CHIP_AREA:
                    cursor = Qt.ArrowCursor
                    self._mouse_pressed_pos = (QPoint(event.pos()),
                            self.grid.get_origin_offset())
                elif int(handle) % 2 == 0:
                    cursor = Qt.SizeBDiagCursor
                else:
                    cursor = Qt.SizeFDiagCursor
                self.pane.widget.setCursor(cursor)
                self._cursor = cursor
                return False
            if self._cursor == self.pane.widget.cursor():
                self.pane.widget.setCursor(Qt.ArrowCursor)
                self._cursor = None
            return True
        # start rotating by pressing the left mouse button, with no modifiers, hovering over one of the corners
        elif ((event.button() == Qt.LeftButton) and
                (event.type() == QEvent.MouseButtonPress) and
                (event.modifiers() == Qt.NoModifier) and
                handle is not None):
            if handle == PaneControlContentGrid.HandleType.CHIP_AREA:
                self.pane.widget.setCursor(Qt.SizeAllCursor)
                self._cursor = Qt.SizeAllCursor
                self._action = PaneControlContentGrid.ACTION_MOVE
            elif self._cursor == Qt.SizeBDiagCursor or self._cursor == Qt.SizeFDiagCursor:
                self._action = PaneControlContentGrid.ACTION_ROTATE
            self._handle = handle
            return True
        # rotate around opposite chip corner with mouse move
        elif ((event.buttons() & Qt.LeftButton) and
                (event.type() == QEvent.MouseMove) and
                (event.modifiers() == Qt.NoModifier) and
                (self._action == PaneControlContentGrid.ACTION_ROTATE)):
            handle = self._handle
            map_handle_to_opposite_corner = [2, 3, 0, 1, 2, 3, 0, 1]
            corner = map_handle_to_opposite_corner[handle]
            angle = (QLineF(event.pos(), corners[corner]).angle() -
                     self.grid.handle_angles[handle] + self.grid.get_angle())
            self.grid.rotate(angle, QPointF(self.grid.get_chip_border()[corner]))
            self.pane.widget.update()
            return True
        # move grid with mouse
        elif ((event.buttons() & Qt.LeftButton) and
                (event.type() == QEvent.MouseMove) and
                (event.modifiers() == Qt.NoModifier) and
                (self._action == PaneControlContentGrid.ACTION_MOVE)):
            delta = event.pos() - self._mouse_pressed_pos[0]
            if Pane.TRANSFORM_SAMPLE_MOTORS in self.pane.shared["transforms"]:
                delta *= self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS].m11()
            if Pane.TRANSFORM_ZOOM in self.pane.shared["transforms"]:
                delta /= self.pane.shared["transforms"][Pane.TRANSFORM_ZOOM].m11()
            self.grid.set_origin_offset(self._mouse_pressed_pos[1] + delta)
            self.pane.widget.update()
            return True
        # stop moving view port, mouse button released
        elif ((event.type() == QEvent.MouseButtonRelease) and
                (self._action == PaneControlContentGrid.ACTION_ROTATE or
                self._action == PaneControlContentGrid.ACTION_MOVE)):
            if self._cursor == self.pane.widget.cursor():
                self.pane.widget.setCursor(Qt.ArrowCursor)
                self._cursor = None
            self._mouse_pressed_pos = None
            self._action = None
            self._handle = None
            return True
    return False
is_handle
is_handle(pos: QPoint) -> Tuple[PaneControlContentGrid.HandleType, List[QPointF]]
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def is_handle(self, pos: QPoint) -> Tuple[PaneControlContentGrid.HandleType, List[QPointF]]:
    if Pane.TRANSFORM_SAMPLE_MOTORS in self.pane.shared["transforms"]:
        x = self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS].m31()
        y = self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS].m32()
        conversion = self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS].m11()
    else:
        x = y = 0
        conversion = 1.0
    transform = QTransform().scale(1. / conversion, 1. / conversion).translate(-x, -y)
    if Pane.TRANSFORM_ZOOM in self.pane.shared["transforms"]:
        transform = transform * self.pane.shared["transforms"][Pane.TRANSFORM_ZOOM]
    transform = transform * self.pane.shared["transforms"][Pane.TRANSFORM_CENTER]
    points = []
    for p in self.grid.get_chip_border()[:-1]:
        points.append(transform.map(p))
    for p in self.grid.get_bounding_box():
        points.append(transform.map(p))
    for i in range(8):
        if QLineF(points[i], pos).length() < PaneControlContentGrid.HANDLE_ACTIVE_RADIUS:
            return (PaneControlContentGrid.HandleType(i), points)
    area = transform.map(self.grid.get_chip_border())
    if area.containsPoint(pos, 0):
        return (PaneControlContentGrid.HandleType.CHIP_AREA, points)
    return (None, points)
paint_event
paint_event(painter: QPainter) -> bool
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def paint_event(self, painter: QPainter) -> bool:
    if self.grid is not None:
        if "conversion" in self.pane.shared:
            conversion = self.pane.shared["conversion"]
        else:
            conversion = 1.
        if Pane.TRANSFORM_ZOOM in self.pane.shared["transforms"]:
            lineweight = (0.5 / (self.pane.shared["transforms"][Pane.TRANSFORM_ZOOM].m11() * conversion ** 0.5) + 0.5)
        else:
            lineweight = 1.
        if Pane.TRANSFORM_SAMPLE_MOTORS in self.pane.shared["transforms"]:
            x = self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS].m31()
            y = self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS].m32()
        else:
            x = y = 0.
        if "beam_size" in self.pane.shared:
            beam_size = self.pane.shared["beam_size"]
        else:
            beam_size = QPointF(10., 10.)
        # find the overlapping rect between camera image and grid in physical units
        if "image_size" in self.pane.shared:
            image_rect = QRectF(
                -self.pane.shared["image_size"].width() / 2.,
                -self.pane.shared["image_size"].height() /2.,
                self.pane.shared["image_size"].width(),
                self.pane.shared["image_size"].height())
        else:
            image_rect = QRectF(-50., -50., 100., 100.)
        transform = QTransform().translate(x, y).scale(1. / conversion, 1. / conversion)
        image_rect = transform.mapRect(image_rect)
        # now for the grid itself, in pixels
        transform = QTransform().scale(conversion, conversion).translate(-x, -y)
        if Pane.TRANSFORM_ZOOM in self.pane.shared["transforms"]:
            transform = transform * self.pane.shared["transforms"][Pane.TRANSFORM_ZOOM]
        transform = transform * self.pane.shared["transforms"][Pane.TRANSFORM_CENTER]
        # finally paint everything
        painter.save()
        painter.setTransform(transform)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(Qt.green, 2. * lineweight))
        painter.drawPolygon(self.grid.get_chip_border())
        painter.setPen(QPen(Qt.red, 2. * lineweight))
        for polygon in self.grid.get_window_borders():
            painter.drawPolygon(polygon)
        painter.setPen(QPen(Qt.green, 1.5 * lineweight))
        grid = self.grid.get_points(invalid=False, ignored=False, rect=image_rect)
        if grid.shape[0] <= 200:
            step = max(1, int(grid.shape[0] / 50))
            for rows in grid[::step]:
                for point in rows[::step]:
                    painter.drawEllipse(QPointF(point[0], point[1]), beam_size.x() / 2., beam_size.y() / 2.)
        painter.setPen(QPen(Qt.yellow, 2. * lineweight))
        for corner in self.grid.get_bounding_box():
            painter.drawEllipse(corner, beam_size.x() / 2., beam_size.y() / 2.)
        if self.center is not None:
            painter.drawEllipse(self.center, 20., 20.)
        painter.restore()
    return False
paint_priority
paint_priority() -> int
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def paint_priority(self) -> int:
    return PaneContent.PRIORITY_GRID
place_grid
place_grid()
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def place_grid(self):
    # adds PaneControlContentPlaceChip to the Pane, which in turn sets a new grid for us
    if self.action_place_grid.isChecked():
        self.pane.add(self._placer)
    else:
        self.pane.remove(self._placer)
register_persistents
register_persistents()
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def register_persistents(self):
    self.pane.utils["persistents"].add_persistent(
        "pane", "grid", self.get_grid, self.set_grid, List)
remove_grid
remove_grid()
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def remove_grid(self):
    if self.grid is not None:
        self.grid.value_changed.disconnect(self._on_value_changed)
    self.grid = None
    self._on_value_changed()
    self.pane.widget.update()
set_grid
set_grid(data: Union[Grid, List]) -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def set_grid(self, data: Union[Grid, List]) -> None:
    if isinstance(data, Grid):
        self.grid = data
    elif len(data) < 4:
        self.grid = None
        return
    elif "chips" in self.pane.utils:
        chip = self.pane.utils["chips"].get_chip(data[0])
        if chip is None:
            self.grid = None
            return
        self.grid = Grid(chip)
        self.grid.set_origin_offset(data[1], data[2])
        self.grid.set_angle(data[3])
    if hasattr(self, "widget_chips_list"):
        self.widget_chips_list.blockSignals(True)
        self.widget_chips_list.setCurrentText(self.grid.chip.name)
        self.widget_chips_list.blockSignals(False)
    if hasattr(self, "action_place_grid") and self.action_place_grid.isChecked():
        self.action_place_grid.setChecked(False)
        self.pane.remove(self._placer)
    self.grid.value_changed.connect(self._on_value_changed)
    self._on_value_changed()
    self.pane.widget.update()
setup_ui
setup_ui() -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def setup_ui(self) -> None:
    self.action_place_grid = self.pane.toolbar.add_action("icons:place_grid.png",
            "Place Grid",self.place_grid, "grid", "mode")
    self.action_remove_grid = self.pane.toolbar.add_action("icons:cross.png",
            "Remove Grid", self.remove_grid, "grid")
    if "chips" in self.pane.utils:
        chip_list = self.pane.utils["chips"].get_chip_list()
        self.widget_chips_list = QComboBox(self.pane.widget)
        for idx, name in enumerate(chip_list):
            self.widget_chips_list.addItem(name)
            self.widget_chips_list.setItemData(idx, name, Qt.ToolTipRole)
        self.widget_chips_list.currentTextChanged.connect(self.exchange_grid)
        self.pane.toolbar.add_widget(self.widget_chips_list, "grid")
        self.action_chip_properties = self.pane.toolbar.add_action(
                "icons:properties.png", "Edit Chip Details", self.remove_grid, "grid")

PaneControlContentPlaceChip

PaneControlContentPlaceChip(parent: PaneControlContentGrid)

Bases: PaneControlContent

Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def __init__(self, parent: PaneControlContentGrid) -> None:
    QObject.__init__(self)
    self.parent = parent
    self.pane = parent.pane
    self.reset()
Attributes
pane instance-attribute
pane = pane
parent instance-attribute
parent = parent
Functions
added
added(pane: Pane) -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def added(self, pane: Pane) -> None:
    self.parent.grid = None
    self.reset()
input_event
input_event(source: QObject, event: QEvent) -> bool
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def input_event(self, source: QObject, event: QEvent) -> bool:
    if not "chips" in self.pane.utils:
        return False
    # mouse left pane, don't draw marker
    if event.type() == QEvent.Leave and self.corner is None:
        self.pointer_pos = None
        self.pane.widget.update()
        return True
    # this is only getting events when active - all mouse events are relevant
    if isinstance(event, QMouseEvent):
        # convert mouse pointers pixel coordinates into motor positions
        pos = QPointF(event.pos())
        center, _ = self.pane.shared["transforms"][Pane.TRANSFORM_CENTER].inverted()
        pos = pos * center
        if Pane.TRANSFORM_ZOOM in self.pane.shared["transforms"]:
            zoom, _ = self.pane.shared["transforms"][Pane.TRANSFORM_ZOOM].inverted()
            pos = pos * zoom
        if Pane.TRANSFORM_SAMPLE_MOTORS in self.pane.shared["transforms"]:
            pos = pos * self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS]
        if event.type() == QEvent.MouseMove:
            self.pointer_pos = pos #draw marker
            if self.corner is not None:
                # compute lines
                line_a = QLineF(self.corner, pos)
                line_b = QLineF(self.corner, pos)
                length = max(self.pane.widget.size().width(), self.pane.widget.size().height())
                if Pane.TRANSFORM_ZOOM in self.pane.shared["transforms"]:
                    length /= self.pane.shared["transforms"][Pane.TRANSFORM_ZOOM].m11()
                if "conversion" in self.pane.shared:
                    length /= self.pane.shared["conversion"]
                line_a.setLength(length)
                line_b.setLength(length)
                angle = line_a.angle()
                if 135. < angle < 315.:
                    self.angle = (angle + 90.) % 360.
                    line_b.setAngle(angle + 90.)
                else:
                    self.angle = angle
                    line_b.setAngle(angle - 90.)
                self.lines = [line_a, line_b]
        elif (event.type() == QEvent.MouseButtonPress and
                event.buttons() == Qt.LeftButton):
            # first click sets corner, second creates grid
            if self.corner is None:
                self.corner = pos
            else:
                name = self.parent.widget_chips_list.currentText()
                chip = self.pane.utils["chips"].get_chip(name)
                if chip is None:
                    grid = None
                grid = Grid(chip)
                grid.set_angle(self.angle)
                offset = grid.get_top_left_offset()
                grid.set_origin_offset(self.corner - offset)
                # we are done, set_grid removes us from pane
                self.parent.set_grid(grid)
                self.reset()
        self.pane.widget.update()
        return True
    return False
paint_event
paint_event(painter: QPainter) -> bool
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def paint_event(self, painter: QPainter) -> bool:
    if not "chips" in self.pane.utils:
        return False
    elif self.pointer_pos is None and self.corner is None:
        return False
    size = self.pane.shared.get("beam_size", QPointF(10., 10.))
    zoom = 1.
    conversion = 1.
    transform = QTransform()
    if Pane.TRANSFORM_SAMPLE_MOTORS in self.pane.shared["transforms"]:
        transform, _ = self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS].inverted()
        conversion = 1. / self.pane.shared["transforms"][Pane.TRANSFORM_SAMPLE_MOTORS].m11()
    if Pane.TRANSFORM_ZOOM in self.pane.shared["transforms"]:
        transform = transform * self.pane.shared["transforms"][Pane.TRANSFORM_ZOOM]
        zoom = self.pane.shared["transforms"][Pane.TRANSFORM_ZOOM].m11()
    transform = transform * self.pane.shared["transforms"][Pane.TRANSFORM_CENTER]
    lineweight = 0.5 / (zoom * conversion ** 0.5) + 0.5
    painter.save()
    painter.setTransform(transform)
    if self.pointer_pos is not None:
        painter.setPen(QPen(Qt.yellow, 2. * lineweight, Qt.SolidLine))
        painter.drawEllipse(self.pointer_pos, size.x() / 2, size.y() / 2)
    if self.corner is not None:
        painter.setPen(QPen(Qt.yellow, 2. * lineweight, Qt.SolidLine))
        painter.drawEllipse(self.corner, size.x() / 2, size.y() / 2)
        if self.lines is not None:
            painter.setPen(QPen(Qt.red, 2. * lineweight, Qt.SolidLine))
            painter.drawLines(self.lines)
    painter.restore()
    return False
paint_priority
paint_priority() -> int
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def paint_priority(self) -> int:
    return PaneContent.PRIORITY_PLACE_CHIP
removed
removed() -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def removed(self) -> None:
    self.reset()
reset
reset() -> None
Source code in mfx/roadrunner/janus/widgets/pane/pane_control_content_grid.py
def reset(self) -> None:
    self.corner = None
    self.lines = None
    self.angle = 0.
    self.pointer_pos = None