Exemplo n.º 1
0
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,
    }
Exemplo n.º 2
0
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)