Esempio n. 1
0
    def __init__(
        self,
        parent,
        mirror_x: bool = True,
        width: int = 800,
        height: int = 600,
        style: int = wx.NO_BORDER,
    ):
        """

        Args:
            parent: wx parent.
            mirror_x: Flip image on its x axis.
            width: width of this panel.
            height: height of this panel.
            style: a style.
        """

        wx.Panel.__init__(self, parent, size=(width, height), style=style)

        self.width = None
        self.height = None

        self.buffer = wx.NullBitmap

        self._mirror_x = mirror_x

        self._matrix = wx.AffineMatrix2D()
        self._buffered_dc = wx.BufferedDC(wx.ClientDC(self), wx.NullBitmap,
                                          wx.BUFFER_VIRTUAL_AREA)
Esempio n. 2
0
    def roundrect_pts(cx, cy, w, h, r, rot, tm):
        """Compute the scaled/translated/rotated endpoints for a rounded rectangle."""

        # Build a rounded rectangle from two overlapping rectangles with
        # four circles to cover the corners.

        # Overlap two rectangles to create a larger rectangle with the corners cut out.
        rects = []
        rects.append(FootprintPainter.rect_pts(cx, cy, w, h - 2 * r, rot, tm))
        rects.append(FootprintPainter.rect_pts(cx, cy, w - 2 * r, h, rot, tm))

        # Add four circles to fill-in corners.
        circles = []
        # Matrix for rotating circle centers and translating by pad center.
        tmp_tm = wx.AffineMatrix2D()
        tmp_tm.Translate(cx, cy)
        tmp_tm.Rotate(rot * math.pi / 180)
        # Circle at NE corner cutout.
        ccx, ccy = tmp_tm.TransformPoint(w / 2 - r, h / 2 - r)
        circles.append(FootprintPainter.circle_pts(ccx, ccy, r, tm))
        # Circle at NW corner cutout.
        ccx, ccy = tmp_tm.TransformPoint(-w / 2 + r, h / 2 - r)
        circles.append(FootprintPainter.circle_pts(ccx, ccy, r, tm))
        # Circle at SE corner cutout.
        ccx, ccy = tmp_tm.TransformPoint(w / 2 - r, -h / 2 + r)
        circles.append(FootprintPainter.circle_pts(ccx, ccy, r, tm))
        # Circle at SW corner cutout.
        ccx, ccy = tmp_tm.TransformPoint(-w / 2 + r, -h / 2 + r)
        circles.append(FootprintPainter.circle_pts(ccx, ccy, r, tm))

        return rects, circles
Esempio n. 3
0
 def test_affinematrix2d1(self):
     m = wx.Matrix2D()
     am = wx.AffineMatrix2D()
     am.Set(m, (23, 25))
     values = am.Get()
     self.assertTrue(len(values) == 2)
     self.assertTrue(isinstance(values[0], wx.Matrix2D))
     self.assertTrue(isinstance(values[1], wx.Point2D))
Esempio n. 4
0
    def rect_pts(cx, cy, w, h, rot, tm):
        """Compute the scaled/translated/rotated endpoints for a rectangle."""

        cx, cy, w, h = [m * PRESCALE for m in [cx, cy, w, h]]
        h2, w2 = h / 2, w / 2
        pts = ((-w2, -h2), (-w2, h2), (w2, h2), (w2, -h2))
        tmp_tm = wx.AffineMatrix2D(tm)
        tmp_tm.Translate(cx, cy)
        tmp_tm.Rotate(rot * math.pi / 180)
        return [list(tmp_tm.TransformPoint(pt)) for pt in pts]
Esempio n. 5
0
    def on_mouse_wheel(self, event):
        if event.GetWheelRotation() > 0:
            zoom_delta = 1.03
        else:
            zoom_delta = 0.97

        # If we just change the zoom, the design will appear to move on the
        # screen.  We have to adjust the pan to compensate.  We want to keep
        # the part of the design under the mouse pointer in the same spot
        # after we zoom, so that we appar to be zooming centered on the
        # mouse pointer.

        # This will create a matrix that takes a point in the design and
        # converts it to screen coordinates:
        matrix = wx.AffineMatrix2D()
        matrix.Translate(*self.pan)
        matrix.Scale(self.zoom, self.zoom)

        # First, figure out where the mouse pointer is in the coordinate system
        # of the design:
        pos = event.GetPosition()
        inverse_matrix = wx.AffineMatrix2D()
        inverse_matrix.Set(*matrix.Get())
        inverse_matrix.Invert()
        pos = inverse_matrix.TransformPoint(*pos)

        # Next, see how that point changes position on screen before and after
        # we apply the zoom change:
        x_old, y_old = matrix.TransformPoint(*pos)
        matrix.Scale(zoom_delta, zoom_delta)
        x_new, y_new = matrix.TransformPoint(*pos)
        x_delta = x_new - x_old
        y_delta = y_new - y_old

        # Finally, compensate for that change in position:
        self.pan = (self.pan[0] - x_delta, self.pan[1] - y_delta)

        self.zoom *= zoom_delta

        self.Refresh()
Esempio n. 6
0
    def trapezoid_pts(cx, cy, w, h, dw, dh, rot, tm):
        """Compute the scaled/translated/rotated endpoints for a trapezoid."""

        cx, cy, w, h, dw, dh = [m * PRESCALE for m in [cx, cy, w, h, dw, dh]]
        h2, w2, dw2, dh2 = h / 2, w / 2, dw / 2, dh / 2
        pts = (
            (-w2 + dw2, -h2 - dh2),
            (-w2 - dw2, h2 + dh2),
            (w2 + dw2, h2 - dh2),
            (w2 - dw2, -h2 + dh2),
        )
        tmp_tm = wx.AffineMatrix2D(tm)
        tmp_tm.Translate(cx, cy)
        tmp_tm.Rotate(rot * math.pi / 180)
        return [list(tmp_tm.TransformPoint(pt)) for pt in pts]
Esempio n. 7
0
    def paint(self, dc, paint_actual_size=False):
        """Paint the footprint into the device context."""

        bbox = self.bbox
        if bbox:
            # The bbox for the footprint exists, so set the
            # scaling and translation for painting it within the DC.
            panel_w, panel_h = dc.GetSize()
            if paint_actual_size:
                scale_x = dc.GetPPI().GetWidth() * 1.0 / (25.4 * PRESCALE)
                scale_y = dc.GetPPI().GetHeight() * 1.0 / (25.4 * PRESCALE)
            else:
                panel_w = panel_w or 100  # Prevent division by zero.
                panel_h = panel_h or 100  # Prevent division by zero.
                scale_x = float(panel_w) / (bbox.x1 - bbox.x0)
                scale_y = float(panel_h) / (bbox.y1 - bbox.y0)
            scale = min(scale_x, scale_y)

            # Compute translation to place footprint center at center of panel.
            bbox_cx, bbox_cy = (bbox.x1 + bbox.x0) / 2, (bbox.y1 + bbox.y0) / 2
            panel_cx, panel_cy = (panel_w / 2) / scale, (panel_h / 2) / scale
            tx, ty = panel_cx - bbox_cx, panel_cy - bbox_cy

            # Transformation matrices (TMs) operate opposite of expected:
            # the first scale/trans operation you add is the last operation
            # that's applied to a point.
            tm = wx.AffineMatrix2D()  # Start with identity matrix.
            tm.Scale(scale,
                     scale)  # Apply scaling from real to screen dimensions.
            tm.Translate(tx, ty)
        else:
            # No bbox, so this must be first time the footprint is being painted.
            # Therefore, paint it without scaling to determine the physical dimensions.
            # These will be used later to scale it to the panel display.
            tm = wx.AffineMatrix2D()  # Identity matrix so no scaling.

        # Initialize device context for drawing.
        dc.SetBackground(wx.Brush(FP_BCK_COLOUR))
        dc.Clear()

        # Create layer dictionary for storing graphics on each layer.
        layers = defaultdict(Layer)

        # Draw the footprint's pad primitives.
        for pad in self.footprint.pads:
            attr = pad.attributes

            # Process the pad.
            w, h = (attr["size"] + [0])[0:2]
            cx, cy, rot = (attr["at"] + [0])[0:3]
            rot = -rot

            # Apply the offset of the pad from the drill.
            try:
                offset_x, offset_y = attr["drill"].attributes["offset"]
            except (AttributeError, KeyError, TypeError):
                pass  # Either no drill or no pad offset w.r.t. the drill.
            else:
                # The pad is offset w.r.t. the drill, so it goes in the opposite dir.
                offset_x, offset_y = -offset_x, -offset_y
                # Rotate the offset as needed.
                rot_tm = wx.AffineMatrix2D()
                rot_tm.Rotate(rot * math.pi / 180)
                offset_x, offset_y = rot_tm.TransformPoint(offset_x, offset_y)
                # Apply the offset to the center of the pad.
                cx -= offset_x
                cy -= offset_y

            # Paint the appropriate pad shape.
            if attr["shape"] == "rect":
                pts = self.rect_pts(cx, cy, w, h, rot, tm)
                for lyr_nm in attr["layers"]:
                    layers[lyr_nm].add_pad_polygon(pts)
            elif attr["shape"] == "circle":
                r = w / 2
                pts = self.circle_pts(cx, cy, r, tm)
                for lyr_nm in attr["layers"]:
                    layers[lyr_nm].add_pad_circle(pts)
            elif attr["shape"] == "roundrect":
                r_ratio = attr["roundrect_rratio"]
                r = r_ratio * min(w, h)
                pts_sets = self.roundrect_pts(cx, cy, w, h, r, rot, tm)
                for lyr_nm in attr["layers"]:
                    for rect_pts in pts_sets[0]:
                        layers[lyr_nm].add_pad_polygon(rect_pts)
                    for circle_pts in pts_sets[1]:
                        layers[lyr_nm].add_pad_circle(circle_pts)
            elif attr["shape"] == "oval":
                r_ratio = 0.5
                r = r_ratio * min(w, h)
                pts_sets = self.roundrect_pts(cx, cy, w, h, r, rot, tm)
                for lyr_nm in attr["layers"]:
                    for rect_pts in pts_sets[0]:
                        layers[lyr_nm].add_pad_polygon(rect_pts)
                    for circle_pts in pts_sets[1]:
                        layers[lyr_nm].add_pad_circle(circle_pts)
            elif attr["shape"] == "trapezoid":
                try:
                    dh, dw = attr["rect_delta"]
                except TypeError:
                    dh, dw = 0, 0
                pts = self.trapezoid_pts(cx, cy, w, h, dw, dh, rot, tm)
                for lyr_nm in attr["layers"]:
                    layers[lyr_nm].add_pad_polygon(pts)
            else:
                raise NotImplementedError

            # Process any drill in the pad.
            cx, cy, rot = (attr["at"] + [0])[0:3]
            try:
                # See if there's a drill in this pad.
                drill_attr = attr["drill"].attributes
            except (KeyError, AttributeError):
                pass  # No drill for this pad.
            else:
                # Yes, there is a drill.
                lyr_nm = "Drill"
                size = drill_attr["size"]
                try:
                    # See if this pad is oval (i.e., has w, h).
                    w, h = size
                    r_ratio = 0.5
                    r = r_ratio * min(w, h)
                    pts_sets = self.roundrect_pts(cx, cy, w, h, r, rot, tm)
                    for rect_pts in pts_sets[0]:
                        layers[lyr_nm].add_pad_polygon(rect_pts)
                    for circle_pts in pts_sets[1]:
                        layers[lyr_nm].add_pad_circle(circle_pts)
                except TypeError:
                    # No, pad is circular.
                    r = size / 2
                    pts = self.circle_pts(cx, cy, r, tm)
                    layers[lyr_nm].add_pad_circle(pts)

        # Draw the footprint's line primitives.
        for line in self.footprint.lines:
            attr = line.attributes
            w = attr["width"]
            x0, y0 = attr["start"]
            x1, y1 = attr["end"]
            pts = self.line_pts(w, x0, y0, x1, y1, tm)
            lyr_nm = attr["layer"]
            layers[lyr_nm].add_line(pts)

        # Draw the footprint's circle primitives.
        for circle in self.footprint.circles:
            attr = circle.attributes
            w = attr["width"]
            cx, cy = attr["center"]
            x1, y1 = attr["end"]
            # Use line_pts routine to calc coords of center and endpoint and line width.
            w, cx, cy, x1, y1 = self.line_pts(w, cx, cy, x1, y1, tm)
            r = math.hypot(cx - x1, cy - y1)
            lyr_nm = attr["layer"]
            layers[lyr_nm].add_circle((w, cx, cy, r))

        # Draw each layer, starting from the bottom layers that will be overwritten by the top layers.
        for lyr_nm in (
                "B.SilkS",
                "B.Fab",
                "B.CrtYd",
                "B.Cu",
                "F&B.Cu",
                "F.Cu",
                "*.Cu",
                "F&B.CrtYd",
                "F.CrtYd",
                "*.CrtYd",
                "F&B.Fab",
                "F.Fab",
                "*.Fab",
                "F&B.SilkS",
                "F.SilkS",
                "*.SilkS",
                "Drill",
        ):
            layers[lyr_nm].set_fill(
                *layer_style[lyr_nm])  # Set layer color and fill.
            layers[lyr_nm].paint(dc)  # Draw items for that layer.

        # layers['F.Mask'].set_fill(FMASK_COLOUR, wx.BRUSHSTYLE_CROSS_HATCH)
        # bmp = self.mk_bmp(4,4,(0,255,0), 1.0/16)
        # layers['F.Paste'].set_fill(bmp)

        if not self.bbox:
            # Calculate the bounding box if it hasn't been set, yet.
            # In this case, the transformation matrix will be an identity matrix,
            # so the bbox will contain the physical size of the footprint.
            # This will be scaled for the given device context when this function
            # is called again.
            self.bbox = BBox(*dc.GetBoundingBox())

            # make the bbox a little bigger to put some margin along each side.
            delta = 0.025 * max(self.bbox.x1 - self.bbox.x0,
                                self.bbox.y1 - self.bbox.y0)

            # Correct point-sized bounding boxes to prevent divide-by-zero errors during scaling.
            if delta < MIN_SIZE:
                delta = MIN_SIZE

            self.bbox = BBox(
                self.bbox.x0 - delta,
                self.bbox.y0 - delta,
                self.bbox.x1 + delta,
                self.bbox.y1 + delta,
            )