def make_shapes(layout, shape_config=None): # First pass to compute some shapes cap_holes = [] raw_cap_holes = [] for _, cluster in layout.key_clusters().items(): hull = [] for k in cluster: key_poly = k.polygon() add_geoms(raw_cap_holes, key_poly) # Buffer the outline to increase the chances of having an # intersection for keys that are very close to each other. # Add a little extra room to minimize the chances that the # keycaps will touch the case/plate edging. hull.append( key_poly.buffer(2, cap_style=CAP_STYLE.square, join_style=JOIN_STYLE.mitre)) # compute the outline of the keycaps; this is for the topmost plate hull = unary_union(MultiPolygon(hull)) # add some spacing to allow the kepcaps room to move through the holes hull = hull.buffer(1, cap_style=CAP_STYLE.square, join_style=JOIN_STYLE.mitre) add_geoms(cap_holes, hull) # The buffer(0) here ensures that the geometry remains valid in the case # that the inflated key hole outlines intersect with each other. cap_holes = MultiPolygon(cap_holes).buffer(0) raw_cap_holes = MultiPolygon(raw_cap_holes).buffer(0) overall_hull = unary_union(cap_holes).convex_hull switch_holes = [] for k in layout.keys(): add_geoms(switch_holes, k.switch_hole()) switch_holes = MultiPolygon(switch_holes).buffer(0) # Now, we want to find somewhere to place the microcontroller. # We can't place it under the keyswitches (unless we want a taller # keyboard; we could make this a configuration option), so we take # the available space from the convex hull and use that as the candidate # space for the components. We expect things to not full fit in this space, # so we're trying to find the location with the largest overlap; once found, # we can include the components in the hull and continue with the rest of # the placement below. def find_space(hull, avoid, shape, padding=5): component_space = hull.symmetric_difference(avoid) bounds = component_space.envelope.bounds best = None for x in range(int(bounds[0]) - padding, int(bounds[2]) + padding): candidate = translate(shape, x, bounds[1]) if avoid.intersects(candidate): continue overlap = component_space.intersection(candidate).area if (not best) or (overlap > best[0]): best = (overlap, candidate) if not best: raise Exception('could not place component') return best[1] mcu_type = shape_config.get('mcu', 'feather') if shape_config else 'feather' if mcu_type == 'feather': mcu_dims = (23, 51) elif mcu_type == 'teensy': mcu_dims = (18, 36) elif mcu_type == 'header': mcu_dims = (5, 25) else: raise Exception('handle mcu type %s' % mcu_type) mcu_coords = shape_config.get('mcu_coords', None) if mcu_coords is None: mcu = translate( find_space(overall_hull, cap_holes, box(0, 0, mcu_dims[0], mcu_dims[1])), 0, 0) else: mcu = box(mcu_coords[0], mcu_coords[1], mcu_dims[0], mcu_dims[1]) #mcu = translate(mcu, 5, 0) # make some space for easier routing # Adjust the hull to fit the mcu overall_hull = unary_union([ overall_hull, mcu.buffer(1, cap_style=CAP_STYLE.square, join_style=JOIN_STYLE.mitre) ]).convex_hull trrs_type = shape_config.get('trrs', None) if shape_config else None if trrs_type == 'basic': trrs_box = box(0, 0, 10, 11) elif trrs_type == 'left+right': trrs_box = box(0, 0, 15, 12) elif trrs_type is None: trrs = None else: raise Exception('handle trrs type %s' % trrs_type) if trrs_type is not None: trrs = find_space(overall_hull, unary_union([switch_holes, mcu]), trrs_box, padding=0) trrs_hull = trrs overall_hull = unary_union([ overall_hull, trrs_hull.buffer(1, cap_style=CAP_STYLE.square, join_style=JOIN_STYLE.mitre) ]).convex_hull rj45_type = shape_config.get('rj45', None) if shape_config else None if rj45_type == 'magjack': rj45_box = box(0, 0, 33, 23) elif rj45_type == 'basic': rj45_box = box(0, 0, 18, 18) elif rj45_type == 'left+right': rj45_box = box(0, 0, 18, 18) elif rj45_type is None: rj45 = None else: raise Exception('handle rj45 type %s' % rj45_type) if rj45_type is not None: rj45 = find_space(overall_hull, unary_union([switch_holes, mcu]), rj45_box, padding=0) rj45_hull = rj45 overall_hull = unary_union([ overall_hull, rj45_hull.buffer(1, cap_style=CAP_STYLE.square, join_style=JOIN_STYLE.mitre) ]).convex_hull # Locate screw holes at the corners. We inflate the hull to allow room for # mounting material. We do half of this now so we can locate the screw # hole centers, then the other half afterwards for the other side. CASE_HOLE_SIZE = 3.0 # M3 screws HOLE_PADDING = 2.0 corner_holes = [] corner_hole_posts = [] overall_hull = overall_hull.buffer((CASE_HOLE_SIZE + HOLE_PADDING) / 2, cap_style=CAP_STYLE.square, join_style=JOIN_STYLE.mitre) # Ensure that sockets are flush with the edge mcu = translate(mcu, 0, -(1 + CASE_HOLE_SIZE + HOLE_PADDING)) if rj45: rj45 = translate(rj45, 0, -(1 + CASE_HOLE_SIZE + HOLE_PADDING)) if trrs: trrs = translate(trrs, 0, -(1 + CASE_HOLE_SIZE + HOLE_PADDING)) corner_points = find_corners(overall_hull) points = [] for c in corner_points: corner_dot = c.buffer(CASE_HOLE_SIZE) if corner_dot.intersects(mcu): continue if rj45 and rj45.intersects(corner_dot): continue if trrs and trrs.intersects(corner_dot): continue points.append(c) corner_points = points for c in corner_points: corner_dot = c.buffer(CASE_HOLE_SIZE / 2) corner_holes.append(corner_dot) corner_hole_posts.append(c.buffer((CASE_HOLE_SIZE + 3) / 2)) corner_holes = MultiPolygon(corner_holes) corner_hole_posts = MultiPolygon(corner_hole_posts) # and take us out the remaining padding, this time we'll round the corners overall_hull = overall_hull.buffer((CASE_HOLE_SIZE + HOLE_PADDING) / 2) bounds = overall_hull.envelope.bounds bounds = (bounds[2] - bounds[0], bounds[3] - bounds[1]) # maximum footprint for the bottom of the case bottom_plate = overall_hull mounting_holes = [] circuit = circuitlib.Circuit() if mcu_type == 'feather': # mounting holes for the mcu feather = circuit.feather() # feather origin is at its center feather.set_position(translate(mcu, 11.5, 26)) feather.set_rotation(90) for _, (pad, padshape, drillshape) in feather._pads_by_idx.items(): if pad.name == "" and drillshape: mounting_holes.append(feather.transform(drillshape)) # mounting holes for the rj45 if rj45_type == 'magjack': jack = circuit.rj45_magjack() jack.set_position(rj45) for _, (pad, padshape, drillshape) in jack._pads_by_idx.items(): if pad.name == "Hole" and drillshape: mounting_holes.append(jack.transform(drillshape)) mounting_holes = MultiPolygon(mounting_holes) top_plate_no_corner_holes = bottom_plate.symmetric_difference(cap_holes) top_plate = top_plate_no_corner_holes.symmetric_difference(corner_holes) switch_plate = cap_holes.symmetric_difference(switch_holes) PCBNEW_SPACING = 25 xlate_bounds = bottom_plate.envelope # the shapes are transformed by this function when # generating the pcb def cxlate(shape): return translate(shape, PCBNEW_SPACING - xlate_bounds.bounds[0], PCBNEW_SPACING - xlate_bounds.bounds[1]) # however, the cirque_coords is expressed in the raw # pcb coordinate space, so we need to reverse that in order that # the shape data is returned with the same origin def rev_cxlate(shape): return translate(shape, -PCBNEW_SPACING + xlate_bounds.bounds[0], -PCBNEW_SPACING + xlate_bounds.bounds[1]) cirque_coords = shape_config.get('cirque_coords', None) if cirque_coords: cirque_coords = rev_cxlate(Point(cirque_coords)) # the aperture for the case cirque_aperture = cirque_coords.buffer(22) # footprint of the touchpad so that it can be # inserted from the rear of the case panel cirque_footprint = cirque_coords.buffer(24.5) else: cirque_aperture = None cirque_footprint = None azoteq_coords = shape_config.get('azoteq_coords', None) if azoteq_coords: azoteq_coords = rev_cxlate(Point(azoteq_coords)) # using the rectangular (almost square) azoteq touchpad? az_inner_width = 41.25 az_inner_height = 38.25 az_outer_width = 44 az_outer_height = 41 azoteq_aperture = translate(box(0, 0, az_inner_width, az_inner_height), azoteq_coords.x - az_inner_width / 2, azoteq_coords.y - az_inner_height / 2) azoteq_footprint = translate( box(0, 0, az_outer_width, az_outer_height), azoteq_coords.x - az_outer_width / 2, azoteq_coords.y - az_outer_height / 2) else: azoteq_aperture = None azoteq_footprint = None return { 'bounds': bounds, 'cap_holes': cap_holes, 'raw_cap_holes': raw_cap_holes, 'top_plate': top_plate, 'top_plate_no_corner_holes': top_plate_no_corner_holes, 'bottom_plate': bottom_plate, 'corner_holes': corner_holes, 'corner_hole_posts': corner_hole_posts, 'corner_points': corner_points, 'switch_plate': switch_plate, 'switch_holes': switch_holes, 'mounting_holes': mounting_holes, 'mcu': mcu, 'rj45': rj45, 'trrs': trrs, 'cirque_coords': cirque_coords, 'cirque_aperture': cirque_aperture, 'cirque_footprint': cirque_footprint, 'azoteq_aperture': azoteq_aperture, 'azoteq_footprint': azoteq_footprint, }
green_multply = [] # create shapely geometry objects for fairways for feature in fairways_data['features']: shape = asShape(feature['geometry']) fairways_multiply.append(shape) # create shapely geometry objects for greens for green in greens_data['features']: green_shape = asShape(green['geometry']) green_multply.append(green_shape) # create shapely MultiPolygon objects for input analysis fairway_plys = MultiPolygon(fairways_multiply) greens_plys = MultiPolygon(green_multply) # run the symmetric difference function creating a new Multipolygon result = fairway_plys.symmetric_difference(greens_plys) # write the results out to well known text (wkt) with shapely dump def write_wkt(filepath, features): with open(filepath, "w") as f: # create a js variable called ply_data used in html # Shapely dumps geometry out to WKT f.write("var ply_data = '" + dumps(features) + "'") # write to our output js file the new polygon as wkt write_wkt(output_wkt_sym_diff, result)
# create storage list for our new shapely objects fairways_multiply = [] green_multply = [] # create shapely geometry objects for fairways for feature in fairways_data['features']: shape = asShape(feature['geometry']) fairways_multiply.append(shape) # create shapely geometry objects for greens for green in greens_data['features']: green_shape = asShape(green['geometry']) green_multply.append(green_shape) # create shapely MultiPolygon objects for input analysis fairway_plys = MultiPolygon(fairways_multiply) greens_plys = MultiPolygon(green_multply) # run the symmetric difference function creating a new Multipolygon result = fairway_plys.symmetric_difference(greens_plys) # write the results out to well known text (wkt) with shapely dump def write_wkt(filepath, features): with open(filepath, "w") as f: # create a js variable called ply_data used in html # Shapely dumps geometry out to WKT f.write("var ply_data = '" + dumps(features) + "'") # write to our output js file the new polygon as wkt write_wkt(output_wkt_sym_diff, result)