Exemple #1
0
    def handle(self, *args, **options):

        with open(options["outline_file"], "r") as outline_file:
            outline = gerber.loads(outline_file.read())

        with open(options["paste_file"], "r") as paste_file:
            paste = gerber.loads(paste_file.read())

        output = gerber_to_scad.process(
            outline,
            paste,
            options["thickness"],
            not options["no_ledge"],
            # form.cleaned_data['ledge_height'],
            # form.cleaned_data['ledge_gap'],
            # form.cleaned_data['increase_hole_size_by'],
            # form.cleaned_data['simplify_regions']
        )

        with open(options["output_file"], "w") as scad_file:
            scad_file.write(output)
Exemple #2
0
def generate_mask(outline, target, scale, bounds, debugimg, status_print,
                  extend_overlay_r_mil, subtract_gerber):
    # Render all gerber layers whose features are to be excluded from the target image, such as board outline, the
    # original silk layer and the solder paste layer to binary images.
    with tempfile.TemporaryDirectory() as tmpdir:
        img_file = path.join(tmpdir, 'target.png')

        status_print('Combining keepout composite')
        fg, bg = gerber.render.RenderSettings(
            (1, 1, 1)), gerber.render.RenderSettings((0, 0, 0))
        ctx = GerberCairoContext(scale=scale)
        status_print('  * outline')
        ctx.render_layer(outline, settings=fg, bgsettings=bg, bounds=bounds)
        status_print('  * target layer')
        ctx.render_layer(target, settings=fg, bgsettings=bg, bounds=bounds)
        for fn, sub in subtract_gerber:
            status_print('  * extra layer', os.path.basename(fn))
            layer = gerber.loads(sub)
            ctx.render_layer(layer, settings=fg, bgsettings=bg, bounds=bounds)
        status_print('Rendering keepout composite')
        ctx.dump(img_file)

        # Vertically flip exported image
        original_img = cv2.imread(img_file, cv2.IMREAD_GRAYSCALE)[::-1, :]

    f = 1 if outline.units == 'inch' else 25.4
    r = 1 + 2 * max(1, int(extend_overlay_r_mil / 1000 * f * scale))
    status_print('Expanding keepout composite by', r)

    # Extend image by a few pixels and flood-fill from (0, 0) to mask out the area outside the outermost outline
    # This ensures no polygons are generated outside the board even for non-rectangular boards.
    border = 10
    outh, outw = original_img.shape
    extended_img = np.zeros((outh + 2 * border, outw + 2 * border),
                            dtype=np.uint8)
    extended_img[border:outh + border, border:outw + border] = original_img
    debugimg(extended_img, 'outline')
    cv2.floodFill(extended_img, None, (0, 0), (255, ))
    original_img = extended_img[border:outh + border, border:outw + border]
    debugimg(extended_img, 'flooded')

    # Dilate the white areas of the image using gaussian blur and threshold. Use these instead of primitive dilation
    # here for their non-directionality.
    target_img = cv2.blur(original_img, (r, r))
    _, target_img = cv2.threshold(target_img, 255 // (1 + r), 255,
                                  cv2.THRESH_BINARY)
    return target_img
Exemple #3
0
    parser.add_argument('-L', '--ledge-height', type=float, default=1.2,
        help='Height of the stencil ledge. This should be less than the '
        'thickness of the PCB (default: %(default)0.1f)')
    parser.add_argument('-g', '--gap', type=float, default=0,
        help='Gap (in mm) between board and stencil ledge. Increase this if '
        'the fit of the stencil is too tight (default: %(default)0.1f)')
    parser.add_argument('-i', '--increase-hole-size', type=float, default=0,
        help='Increase the size of all holes in the stencil by this amount (in '
        'mm). Use this if you find holes get printed smaller than they should '
        '(default: %(default)0.1f)')

    args = parser.parse_args()

    outline_file = open(args.outline_file, 'rU')
    solderpaste_file = open(args.solderpaste_file, 'rU')

    outline = gerber.loads(outline_file.read())
    solder_paste = gerber.loads(solderpaste_file.read())
    with open(args.output_file, 'w') as output_file:
        output_file.write(
            process(
                outline,
                solder_paste,
                args.thickness,
                args.include_ledge,
                args.ledge_height,
                args.gap,
                args.increase_hole_size
            )
        )
Exemple #4
0
def paste_image(target_gerber: str,
                outline_gerber: str,
                source_img: np.ndarray,
                subtract_gerber: list = [],
                extend_overlay_r_mil: float = 6,
                extend_picture_r_mil: float = 2,
                status_print=lambda *args: None,
                debugdir: str = None):

    debugctr = 0

    def debugimg(img, name):
        nonlocal debugctr
        if debugdir:
            cv2.imwrite(
                path.join(debugdir, '{:02d}{}.png'.format(debugctr, name)),
                img)
        debugctr += 1

    # Parse outline layer to get bounds of gerber file
    status_print('Parsing outline gerber')
    outline = gerber.loads(outline_gerber)
    bounds = (minx, maxx), (miny, maxy) = outline.bounding_box
    grbw, grbh = maxx - minx, maxy - miny
    status_print('  * outline has offset {}, size {}'.format((minx, miny),
                                                             (grbw, grbh)))

    # Parse target layer
    status_print('Parsing target gerber')
    target = gerber.loads(target_gerber)
    (tminx, tmaxx), (tminy, tmaxy) = target.bounding_box
    status_print('  * target layer has offset {}, size {}'.format(
        (tminx, tminy), (tmaxx - tminx, tmaxy - tminy)))

    # Read source image
    imgh, imgw = source_img.shape
    scale = math.ceil(max(imgw / grbw, imgh / grbh))  # scale is in dpmm
    status_print('  * source image has size {}, going for scale {}dpmm'.format(
        (imgw, imgh), scale))

    # Merge layers to target mask
    target_img = generate_mask(outline, target, scale, bounds, debugimg,
                               status_print, extend_overlay_r_mil,
                               subtract_gerber)

    # Threshold source image. Ideally, the source image is already binary but in case it's not, or in case it's not
    # exactly binary (having a few very dark or very light grays e.g. due to JPEG compression) we're thresholding here.
    status_print('Thresholding source image')
    qr = 1 + 2 * max(1, int(extend_picture_r_mil / 1000 * scale))
    source_img = source_img[::-1]
    _, source_img = cv2.threshold(source_img, 127, 255, cv2.THRESH_BINARY)
    debugimg(source_img, 'thresh')

    # Pad image to size of target layer images generated above. After this, `scale` applies to the padded image as well
    # as the gerber renders. For padding, zoom or shrink the image to completely fit the gerber's rectangular bounding
    # box. Center the image vertically or horizontally if it has a different aspect ratio.
    status_print('Padding source image')
    tgth, tgtw = target_img.shape
    padded_img = np.zeros(shape=target_img.shape, dtype=source_img.dtype)
    offx = int((minx - tminx if tminx < minx else 0) * scale)
    offy = int((miny - tminy if tminy < miny else 0) * scale)
    offx += int(grbw * scale - imgw) // 2
    offy += int(grbh * scale - imgh) // 2
    endx, endy = min(offx + imgw, tgtw), min(offy + imgh, tgth)
    print('off', (offx, offy), 'end', (endx, endy), 'img', (imgw, imgh), 'tgt',
          (tgtw, tgth))
    padded_img[offy:endy, offx:endx] = source_img[:endy - offy, :endx - offx]
    debugimg(padded_img, 'padded')
    debugimg(target_img, 'target')

    # Mask out excluded gerber features (source silk, holes, solder mask etc.) from the target image
    status_print('Masking source image')
    out_img = (np.multiply(
        (padded_img / 255.0),
        (target_img / 255.0) * -1 + 1) * 255).astype(np.uint8)

    debugimg(out_img, 'multiplied')

    # Calculate contours from masked target image and plot them to the target gerber context
    status_print('Calculating contour lines')
    plot_contours(out_img,
                  target,
                  offx=(minx, miny),
                  scale=scale,
                  status_print=lambda *args: status_print('   ', *args))

    # Write target gerber context to disk
    status_print('Generating output gerber')
    from gerber.render import rs274x_backend
    ctx = rs274x_backend.Rs274xContext(target.settings)
    target.render(ctx)
    out = ctx.dump().getvalue()
    status_print('Done.')
    return out
Exemple #5
0
def main(request):
    form = UploadForm(request.POST or None, files=request.FILES or None)
    version = _get_version()
    if form.is_valid():
        outline_file = form.cleaned_data["outline_file"]
        solderpaste_file = request.FILES["solderpaste_file"]

        try:
            outline = gerber.loads(outline_file.read().decode("utf-8"))
        except Exception as e:
            logging.error(e)
            outline = None
            form.errors["outline_file"] = [
                "Invalid format, is this a valid gerber file?"
            ]

        try:
            solder_paste = gerber.loads(
                solderpaste_file.read().decode("utf-8"))
        except Exception as e:
            logging.error(e)
            solder_paste = None
            form.errors["solderpaste_file"] = [
                "Invalid format, is this a valid gerber file?"
            ]

        if outline and solder_paste:
            output = process_gerber(
                outline,
                solder_paste,
                form.cleaned_data["stencil_thickness"],
                form.cleaned_data["include_ledge"],
                form.cleaned_data["ledge_height"],
                form.cleaned_data["ledge_gap"],
                form.cleaned_data["increase_hole_size_by"],
                form.cleaned_data["simplify_regions"],
                flip_stencil=form.cleaned_data["flip_stencil"],
            )

            file_id = randint(1000000000, 9999999999)
            scad_filename = "/tmp/gts-{}.scad".format(file_id)
            stl_filename = "/tmp/gts-{}.stl".format(file_id)

            with open(scad_filename, "w") as scad_file:
                scad_file.write(output)

            p = subprocess.Popen([
                settings.OPENSCAD_BIN,
                "-o",
                stl_filename,
                scad_filename,
            ])
            p.wait()

            if p.returncode:
                form.errors["__all__"] = [
                    "Failed to create an STL file from inputs"
                ]
            else:
                with open(stl_filename, "r") as stl_file:
                    stl_data = stl_file.read()
                os.remove(stl_filename)

            # Clean up temporary files
            os.remove(scad_filename)

        if form.errors:
            return render(request, "main.html", {
                "form": form,
                "version": version
            })

        response = HttpResponse(stl_data, content_type="application/zip")
        response["Content-Disposition"] = ("attachment; filename=%s" %
                                           stl_filename.rsplit("/")[-1])
        return response

    return render(request, "main.html", {"form": form, "version": version})
    parser.add_argument('-L', '--ledge-height', type=float, default=1.2,
        help='Height of the stencil ledge. This should be less than the '
        'thickness of the PCB (default: %(default)0.1f)')
    parser.add_argument('-g', '--gap', type=float, default=0,
        help='Gap (in mm) between board and stencil ledge. Increase this if '
        'the fit of the stencil is too tight (default: %(default)0.1f)')
    parser.add_argument('-i', '--increase-hole-size', type=float, default=0,
        help='Increase the size of all holes in the stencil by this amount (in '
        'mm). Use this if you find holes get printed smaller than they should '
        '(default: %(default)0.1f)')

    args = parser.parse_args()

    outline_file = open(args.outline_file, 'rU')
    solderpaste_file = open(args.solderpaste_file, 'rU')

    outline = gerber.loads(outline_file.read())
    solder_paste = gerber.loads(solderpaste_file.read())
    with open(args.output_file, 'w') as output_file:
        output_file.write(
            process(
                outline,
                solder_paste,
                args.thickness,
                args.include_ledge,
                args.ledge_height,
                args.gap,
                args.increase_hole_size
            )
        )
def main(request):
    form = UploadForm(request.POST or None, files=request.FILES or None)
    if form.is_valid():
        outline_file = form.cleaned_data['outline_file']
        solderpaste_file = request.FILES['solderpaste_file']

        try:
            outline = gerber.loads(outline_file.read())
        except:
            outline = None
            form.errors['outline_file'] = [
                "Invalid format, is this a valid gerber file?"
            ]

        try:
            solder_paste = gerber.loads(solderpaste_file.read())
        except:
            solder_paste = None
            form.errors['solderpaste_file'] = [
                "Invalid format, is this a valid gerber file?"
            ]

        if outline and solder_paste:
            output = process(outline, solder_paste,
                             form.cleaned_data['stencil_thickness'],
                             form.cleaned_data['include_ledge'],
                             form.cleaned_data['ledge_height'],
                             form.cleaned_data['ledge_gap'],
                             form.cleaned_data['increase_hole_size_by'])

            file_id = randint(1000000000, 9999999999)
            scad_filename = '/tmp/gts-{}.scad'.format(file_id)
            stl_filename = '/tmp/gts-{}.stl'.format(file_id)

            with open(scad_filename, 'w') as scad_file:
                scad_file.write(output)

            p = subprocess.Popen([
                settings.OPENSCAD_BIN,
                '-o',
                stl_filename,
                scad_filename,
            ])
            p.wait()

            if p.returncode:
                form.errors['__all__'] = [
                    "Failed to create an STL file from inputs"
                ]
            else:
                with open(stl_filename, 'r') as stl_file:
                    stl_data = stl_file.read()
                os.remove(stl_filename)

            # Clean up temporary files
            os.remove(scad_filename)

        if form.errors:
            return render(request, "main.html", {'form': form})

        response = HttpResponse(stl_data, content_type='application/zip')
        response[
            'Content-Disposition'] = 'attachment; filename=%s' % stl_filename.rsplit(
                "/")[-1]
        return response

    return render(request, "main.html", {'form': form})
def main(request):
    form = UploadForm(request.POST or None, files=request.FILES or None)
    if form.is_valid():
        outline_file = form.cleaned_data['outline_file']
        solderpaste_file = request.FILES['solderpaste_file']

        try:
            outline = gerber.loads(outline_file.read())
        except:
            outline = None
            form.errors['outline_file'] = ["Invalid format, is this a valid gerber file?"]

        try:
            solder_paste = gerber.loads(solderpaste_file.read())
        except:
            solder_paste = None
            form.errors['solderpaste_file'] = ["Invalid format, is this a valid gerber file?"]

        if outline and solder_paste:
            output = process(
                outline,
                solder_paste,
                form.cleaned_data['stencil_thickness'],
                form.cleaned_data['include_ledge'],
                form.cleaned_data['ledge_height'],
                form.cleaned_data['ledge_gap'],
                form.cleaned_data['increase_hole_size_by']
            )

            file_id = randint(1000000000, 9999999999)
            scad_filename = '/tmp/gts-{}.scad'.format(file_id)
            stl_filename = '/tmp/gts-{}.stl'.format(file_id)

            with open(scad_filename, 'w') as scad_file:
                scad_file.write(output)

            p = subprocess.Popen([
                settings.OPENSCAD_BIN,
                '-o',
                stl_filename,
                scad_filename,
            ])
            p.wait()

            if p.returncode:
                form.errors['__all__'] = ["Failed to create an STL file from inputs"]
            else:
                with open(stl_filename, 'r') as stl_file:
                    stl_data = stl_file.read()
                os.remove(stl_filename)

            # Clean up temporary files
            os.remove(scad_filename)

        if form.errors:
            return render(request, "main.html", {'form': form})

        response = HttpResponse(stl_data, content_type='application/zip')
        response['Content-Disposition'] = 'attachment; filename=%s' % stl_filename.rsplit("/")[-1]
        return response

    return render(request, "main.html", {'form': form})