import sys import cadquery as cq import math as m from cadquery.occ_impl.shapes import Face from OCP.TopExp import TopExp from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape import OCP.TopAbs as ta from cqkit import get_box_selector def _vertAfterEdge(face, edge): ea, eb = edge.Vertices() edges = face.Edges() ni = next(i for i,e in enumerate(edges) if e.isSame(edge)) ni = (ni + 1) % len(edges) na, nb = edges[ni].Vertices() if na.isSame(ea) or na.isSame(eb): return nb else: return na def filterEdgeConvexity(self, convex=True): filtered_edges = [] edge_face_map = TopTools_IndexedDataMapOfShapeListOfShape() TopExp.MapShapesAndAncestors_s( self.findSolid().wrapped, ta.TopAbs_EDGE, ta.TopAbs_FACE, edge_face_map ) for edge in self.objects: faces = edge_face_map.FindFromKey(edge.wrapped) if faces.Size() != 2: raise ArgumentError("invalid face count") a, b = [Face(f) for f in faces] av = _vertAfterEdge(a, edge).Center() bv = _vertAfterEdge(b, edge).Center() disc = (bv - av).dot(a.normalAt()) if (disc < 0) == convex: filtered_edges.append(edge) return self.newObject(filtered_edges) def filletChamferComp(self, radius, chamfer): ''' radius is the radius of the smaller fillet ''' return ( self .tag('_filletChamferComp') .filterEdgeConvexity(False) .fillet(radius) .edges(tag='_filletChamferComp') .fillet(radius + chamfer) ) cq.Workplane.filterEdgeConvexity = filterEdgeConvexity cq.Workplane.filletChamferComp = filletChamferComp spacing = 21.5 size = spacing / m.sqrt(3) radius = 2 * size expand = 1 fillet = 3 chamfer = 1 switch_sit = 2.2 top_thick = 1.3 top_space = switch_sit - top_thick pcb_thick = 1.6 bottom_space = 2 bottom_thick = 3 oled_thick = 6.05 feet_d = 11 def shape2points(shape): lines = shape.strip('\n').split('\n') for y, line in enumerate(lines): for x, char in enumerate(line): if char != ' ': yield x, len(lines) - y - 1 def gridpos(pos, ox=0, oy=0): x, y = pos return (y * -1.5 * size - oy, x * spacing / 2 + ox) def gridify(gen): for x, y in gen: yield (y * -1.5 * size, x * spacing / 2) def v2kicad(p): x, y, _ = p.toTuple() return '(xy {:.4} {:.4})'.format(x, -y) def edges_to_loop(wire): v2e = {} e2v = {} for edge in wire.Edges(): e = hash(edge) e2v[e] = [] for vert in edge.Vertices(): v = v2kicad(vert) if v not in v2e: v2e[v] = [] e2v[e].append(v) v2e[v].append(edge) last_edge = wire.Edges()[0] last_point = v2kicad(last_edge.Vertices()[0]) first_point = last_point points = [last_point] while True: verts = e2v[hash(last_edge)] if verts[0] == last_point: last_point = verts[1] else: last_point = verts[0] if last_point == first_point: break last_edge = next(e for e in v2e[last_point] if hash(e) != hash(last_edge)) points.append(last_point) return points def make_plate( shape, cx=0, cy=0, outputs=[], **kwargs, ): results = [] key_pos = list(gridify(shape2points(shape))) plate = ( cq.Workplane("XY") .pushPoints(key_pos) .polygon(6, radius) .extrude(1) ) if 'pre_top' in kwargs: plate = kwargs['pre_top'](plate, cx, cy) top = plate.faces('Z') .workplane() .pushPoints([ gridpos((7, 5.2)), gridpos((13, 5.2)) ]) .cboreHole(3.4, 6, 3.5) # mode indicator .moveTo(*gridpos((2, 5.185))) .circle(5.850) .cutThruAll() ) cover_final = (cover .rotate((0,0,0), (0,0,1), -90) .translate((0, 0, top_space+top_thick)) ) if 'cover' in outputs: results.append(('cover', 'step', cover_final)) results.append(('cover', 'dxf', cover_final.section(top_space+top_thick - 1.5))) results.append(('cover.cb', 'dxf', cover_final.section(top_space+top_thick - 3.5))) if 'fill.outer' in outputs: wire = ( cq.Workplane("XY") .newObject([bottom]) .toPending() .offset2D(expand, kind='intersection') .extrude(1) # fillets .edges('|Z') .fillet(fillet) .rotate((0,0,0), (0,0,1), -90) .faces(' 1: outputs = sys.argv[1:] for output in make_plate(**design, outputs=outputs): name, fmt, obj = output[:3] opt = None if len(output) > 3: opt = output[3] filename = '{}.{}'.format(name, fmt) print('generating "{}"...'.format(filename)) if fmt == 'str': with open(filename, 'w') as f: f.write(obj) else: cq.exporters.export(obj, filename, opt=opt)