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)
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
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 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
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})
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})