From fba189c69534fe0e88851e35716fe0e5d07a5c98 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 9 Apr 2023 17:24:50 +0200 Subject: protogen web interface works --- gerbonara/cad/primitives.py | 28 +- gerbonara/cad/protoboard.py | 17 +- gerbonara/cad/protoserve.py | 131 ++++ gerbonara/cad/protoserve_data/protoserve.html | 899 ++++++++++++++++++++++++++ 4 files changed, 1055 insertions(+), 20 deletions(-) create mode 100644 gerbonara/cad/protoserve.py create mode 100644 gerbonara/cad/protoserve_data/protoserve.html (limited to 'gerbonara/cad') diff --git a/gerbonara/cad/primitives.py b/gerbonara/cad/primitives.py index b566d76..abce1a4 100644 --- a/gerbonara/cad/primitives.py +++ b/gerbonara/cad/primitives.py @@ -342,17 +342,21 @@ class THTPad(Pad): x, y, rotation = self.abs_pos self.pad_top.parent = self self.pad_top.render(layer_stack) - self.pad_bottom.parent = self - self.pad_bottom.render(layer_stack) + if self.pad_bottom: + self.pad_bottom.parent = self + self.pad_bottom.render(layer_stack) if self.aperture_inner is None: (x_min, y_min), (x_max, y_max) = self.pad_top.bounding_box(MM) w_top = x_max - x_min h_top = y_max - y_min - (x_min, y_min), (x_max, y_max) = self.pad_bottom.bounding_box(MM) - w_bottom = x_max - x_min - h_bottom = y_max - y_min - self.aperture_inner = CircleAperture(min(w_top, h_top, w_bottom, h_bottom), unit=MM) + if self.pad_bottom: + (x_min, y_min), (x_max, y_max) = self.pad_bottom.bounding_box(MM) + w_bottom = x_max - x_min + h_bottom = y_max - y_min + w_top = min(w_top, w_bottom) + h_top = min(h_top, h_bottom) + self.aperture_inner = CircleAperture(min(w_top, h_top), unit=MM) for (side, use), layer in layer_stack.inner_layers: layer.objects.append(Flash(x, y, self.aperture_inner.rotated(rotation), unit=self.unit)) @@ -368,24 +372,24 @@ class THTPad(Pad): return False @classmethod - def rect(kls, x, y, hole_dia, w, h=None, rotation=0, mask_expansion=0.0, paste_expansion=0.0, paste=True, unit=MM): + def rect(kls, x, y, hole_dia, w, h=None, rotation=0, mask_expansion=0.0, paste_expansion=0.0, paste=True, plated=True, unit=MM): if h is None: h = w pad = SMDPad.rect(0, 0, w, h, mask_expansion=mask_expansion, paste_expansion=paste_expansion, paste=paste, unit=unit) - return kls(x, y, hole_dia, pad, rotation=rotation, unit=unit) + return kls(x, y, hole_dia, pad, rotation=rotation, plated=plated, unit=unit) @classmethod - def circle(kls, x, y, hole_dia, dia, mask_expansion=0.0, paste_expansion=0.0, paste=True, unit=MM): + def circle(kls, x, y, hole_dia, dia, mask_expansion=0.0, paste_expansion=0.0, paste=True, plated=True, unit=MM): pad = SMDPad.circle(0, 0, dia, mask_expansion=mask_expansion, paste_expansion=paste_expansion, paste=paste, unit=unit) - return kls(x, y, hole_dia, pad, unit=unit) + return kls(x, y, hole_dia, pad, plated=plated, unit=unit) @classmethod - def obround(kls, x, y, hole_dia, w, h, rotation=0, mask_expansion=0.0, paste_expanson=0.0, paste=True, unit=MM): + def obround(kls, x, y, hole_dia, w, h, rotation=0, mask_expansion=0.0, paste_expanson=0.0, paste=True, plated=True, unit=MM): ap_c = CircleAperture(dia, unit=unit) ap_m = CircleAperture(dia+2*mask_expansion, unit=unit) ap_p = CircleAperture(dia+2*paste_expansion, unit=unit) if paste else None pad = SMDPad(0, 0, side='top', copper_aperture=ap_c, mask_aperture=ap_m, paste_aperture=ap_p, unit=unit) - return kls(x, y, hole_dia, pad, rotation=rotation, unit=unit) + return kls(x, y, hole_dia, pad, rotation=rotation, plated=plated, unit=unit) @dataclass diff --git a/gerbonara/cad/protoboard.py b/gerbonara/cad/protoboard.py index 41276e2..a3d616f 100644 --- a/gerbonara/cad/protoboard.py +++ b/gerbonara/cad/protoboard.py @@ -57,9 +57,9 @@ class PropLayout: first = bool(i == 0) last = bool(i == len(self.content)-1) yield from child.generate(bbox, ( - border_text[0] and (first or self.direction == 'h'), + border_text[0] and (last or self.direction == 'h'), border_text[1] and (last or self.direction == 'v'), - border_text[2] and (last or self.direction == 'h'), + border_text[2] and (first or self.direction == 'h'), border_text[3] and (first or self.direction == 'v'), ), unit) @@ -493,19 +493,20 @@ def eval_value(value, total_length=None): def _demo(): - #pattern1 = PatternProtoArea(2.54, obj=THTPad.circle(0, 0, 0.9, 1.8, paste=False)) + pattern1 = PatternProtoArea(2.54, obj=THTPad.circle(0, 0, 0.9, 1.8, paste=False)) pattern2 = PatternProtoArea(1.2, 2.0, obj=SMDPad.rect(0, 0, 1.0, 1.8, paste=False)) - #pattern3 = PatternProtoArea(2.54, 1.27, obj=SMDPad.rect(0, 0, 2.3, 1.0, paste=False)) - pattern3 = EmptyProtoArea(copper_fill=True) - stack = TwoSideLayout(pattern2, pattern3) - #pattern = PropLayout([pattern1, stack], 'h', [0.5, 0.5]) + pattern3 = PatternProtoArea(2.54, 1.27, obj=SMDPad.rect(0, 0, 2.3, 1.0, paste=False)) + #pattern3 = EmptyProtoArea(copper_fill=True) + #stack = TwoSideLayout(pattern2, pattern3) + stack = PropLayout([pattern2, pattern3], 'v', [0.5, 0.5]) + pattern = PropLayout([pattern1, stack], 'h', [0.5, 0.5]) #pattern = PatternProtoArea(2.54, obj=ManhattanPads(2.54)) #pattern = PatternProtoArea(2.54, obj=PoweredProto()) #pattern = PatternProtoArea(2.54, obj=RFGroundProto()) #pattern = PatternProtoArea(2.54*1.5, obj=THTFlowerProto()) #pattern = PatternProtoArea(2.54, obj=THTPad.circle(0, 0, 0.9, 1.8, paste=False)) #pattern = PatternProtoArea(2.54, obj=PoweredProto()) - pb = ProtoBoard(100, 80, stack, mounting_hole_dia=3.2, mounting_hole_offset=5) + pb = ProtoBoard(100, 80, pattern, mounting_hole_dia=3.2, mounting_hole_offset=5) print(pb.pretty_svg()) pb.layer_stack().save_to_directory('/tmp/testdir') diff --git a/gerbonara/cad/protoserve.py b/gerbonara/cad/protoserve.py new file mode 100644 index 0000000..0bf9dce --- /dev/null +++ b/gerbonara/cad/protoserve.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +import importlib.resources +from tempfile import TemporaryDirectory +from pathlib import Path + +from quart import Quart, request, Response + +from . import protoboard as pb +from . import protoserve_data +from ..utils import MM, Inch + + +def extract_importlib(package): + root = TemporaryDirectory() + + stack = [(importlib.resources.files(package), Path(root.name))] + while stack: + res, out = stack.pop() + + for item in res.iterdir(): + item_out = out / item.name + if item.is_file(): + item_out.write_bytes(item.read_bytes()) + else: + assert item.is_dir() + item_out.mkdir() + stack.push((item, item_out)) + + return root + +static_folder = extract_importlib(protoserve_data) +app = Quart(__name__, static_folder=static_folder.name) + +@app.route('/') +async def index(): + return await app.send_static_file('protoserve.html') + +def deserialize(obj, unit): + pitch_x = float(obj.get('pitch_x', 1.27)) + pitch_y = float(obj.get('pitch_y', 1.27)) + clearance = float(obj.get('clearance', 0.2)) + + match obj['type']: + case 'layout': + proportions = [float(child['layout_prop']) for child in obj['children']] + content = [deserialize(child, unit) for child in obj['children']] + return pb.PropLayout(content, obj['direction'], proportions) + + case 'placeholder': + return pb.EmptyProtoArea() + + case 'smd': + match obj['pad_shape']: + case 'rect': + pad = pb.SMDPad.rect(0, 0, pitch_x-clearance, pitch_y-clearance, paste=False, unit=unit) + case 'circle': + pad = pb.SMDPad.circle(0, 0, min(pitch_x, pitch_y)-clearance, paste=False, unit=unit) + return pb.PatternProtoArea(pitch_x, pitch_y, obj=pad, unit=unit) + + case 'tht': + hole_dia = float(obj['hole_dia']) + match obj['plating']: + case 'plated': + oneside, plated = False, True + case 'nonplated': + oneside, plated = False, False + case 'singleside': + oneside, plated = True, False + + match obj['pad_shape']: + case 'rect': + pad = pb.THTPad.rect(0, 0, hole_dia, pitch_x-clearance, pitch_y-clearance, paste=False, plated=plated, unit=unit) + case 'circle': + pad = pb.THTPad.circle(0, 0, hole_dia, min(pitch_x, pitch_y)-clearance, paste=False, plated=plated, unit=unit) + case 'obround': + pad = pb.THTPad.obround(0, 0, hole_dia, pitch_x-clearance, pitch_y-clearance, paste=False, plated=plated, unit=unit) + + if oneside: + pad.pad_bottom = None + + return pb.PatternProtoArea(pitch_x, pitch_y, obj=pad, unit=unit) + + case 'manhattan': + return pb.PatternProtoArea(pitch_x, pitch_y, obj=pb.ManhattanPads(pitch_x, pitch_y, clearance, unit=unit), unit=unit) + + case 'powered': + pitch = float(obj.get('pitch', 2.54)) + hole_dia = float(obj['hole_dia']) + via_drill = float(obj['via_hole_dia']) + trace_width = float(obj['trace_width']) + return pb.PatternProtoArea(pitch, pitch, pb.PoweredProto(pitch, hole_dia, clearance, via_size=via_drill, trace_width=trace_width, unit=unit), unit=unit) + + case 'flower': + pitch = float(obj.get('pitch', 2.54)) + hole_dia = float(obj['hole_dia']) + pattern_dia = float(obj['pattern_dia']) + return pb.PatternProtoArea(2*pitch, 2*pitch, pb.THTFlowerProto(pitch, hole_dia, pattern_dia, unit=unit), unit=unit) + + case 'rf': + pitch = float(obj.get('pitch', 2.54)) + hole_dia = float(obj['hole_dia']) + via_dia = float(obj['via_dia']) + via_drill = float(obj['via_hole_dia']) + return pb.PatternProtoArea(pitch, pitch, pb.RFGroundProto(pitch, hole_dia, clearance, via_dia, via_drill, unit=MM), unit=MM) + +@app.route('/preview.svg', methods=['POST']) +async def preview(): + obj = await request.get_json() + + unit = Inch if obj.get('units' == 'us') else MM + w = float(obj.get('width', unit(100, MM))) + h = float(obj.get('height', unit(80, MM))) + corner_radius = float(obj.get('round_corners', {}).get('radius', unit(1.5, MM))) + holes = obj.get('mounting_holes', {}) + mounting_hole_dia = float(holes.get('diameter', unit(3.2, MM))) + mounting_hole_offset = float(holes.get('offset', unit(5, MM))) + + content = deserialize(obj['children'][0], unit) + + board = pb.ProtoBoard(w, h, content, + corner_radius=corner_radius, + mounting_hole_dia=mounting_hole_dia, + mounting_hole_offset=mounting_hole_offset, + unit=unit) + return Response(str(board.pretty_svg()), mimetype='image/svg+xml') + + +if __name__ == '__main__': + app.run() + diff --git a/gerbonara/cad/protoserve_data/protoserve.html b/gerbonara/cad/protoserve_data/protoserve.html new file mode 100644 index 0000000..4b116b2 --- /dev/null +++ b/gerbonara/cad/protoserve_data/protoserve.html @@ -0,0 +1,899 @@ + + + + Protoserve + + + + + + +
+
+

Board settings

+ + + + + + +
+ + + +
+ +
+ + + + + +
+ +

Content

+
+
+
+
+ Automatically generated preview image +
+ + + + + + + + + + + + + + + + + + + + + + + + + -- cgit