示例#1
0
def calc_temcor_side(pgon, pyramid_ht, base_ht, freq):
    freq += 1
    inrad = pgon.inradius()
    axis = Vec(-inrad, 0, pyramid_ht)           # axis to rotate plane around
    A = Vec(0, 0, base_ht + pyramid_ht)         # apex
    B = Vec(inrad, 0.5, base_ht)                # base polygon vertex

    n0 = Vec.cross(A, B).unit()

    edge_ang = anti_lib.angle_around_axis(Vec(B[0], 0, B[2]), B, axis)
    ang_inc = edge_ang/freq

    points = []
    faces = []
    for i in range(freq):
        n_inc = Mat.rot_axis_ang(axis, i*ang_inc) * Vec(0, 1, 0)
        edge_v = Vec.cross(n_inc, n0).unit()
        last_idx = i*(i-1)//2
        new_idx = i*(i+1)//2
        for j in range(i + 1):
            v = Mat.rot_axis_ang(axis, -2*j*ang_inc) * edge_v
            points.append(v)
            if new_idx and j < i:
                faces.append([new_idx+j, new_idx+j+1, last_idx+j])
            if j < i-1:
                faces.append([new_idx+j+1, last_idx+j+1, last_idx+j])

    return (points, faces)
示例#2
0
def j89_get_principal_verts(pgon, ang, flags):
    bad = flags.strip('ABC')
    if bad:
        raise ValueError('Unrecognised form flag(s) \'' + bad + '\'')
    ridge_down = 'A' in flags
    ridge2_down = 'B' in flags
    belt_back = 'C' in flags
    pgon.N *= 2
    # p_ang2 = pgon2.angle()
    A, B, B2, C = get_pstt_cap_verts(pgon, ang, ridge_down)
    pgon.N //= 2

    p_ang = pgon.angle() / 2
    A2_rad = pgon.circumradius(edge)  # circumradius of top polygon
    sq_mid = (B + B2) / 2
    sq_mid_rad = Vec(sq_mid[0], sq_mid[1], 0).mag()
    tri_ht2 = 3 / 4 - (A2_rad - sq_mid_rad)**2
    if tri_ht2 < -epsilon:
        raise ValueError(
            'Not possible to calculate upper equilateral triangle')

    A2_ht = (1 - 2 * belt_back) * sqrt(tri_ht2) + B[2]
    A2 = Vec(A2_rad, 0, A2_ht).rot_z(p_ang / 2)

    A2_minus1 = A2.rot_z(-2 * p_ang)
    B2_minus1 = B2.rot_z(-2 * p_ang)
    C2 = get_three_line_vert(A2_minus1, A2, B2_minus1, B, ridge2_down)
    return [pt.rot_z(p_ang / 2) for pt in [A, B, B2, C, A2, C2]]
示例#3
0
def j88_get_principal_verts(pgon, ang, flags):
    bad = flags.strip('AB')
    if bad:
        raise ValueError('Unrecognised form flag(s) \'' + bad + '\'')
    ridge = 'A' not in flags
    belt_back = 'B' in flags
    A, B, B2, C = get_pstts_cap_verts(pgon, ang, ridge)

    p_ang = pgon.angle() / 2
    sq_mid = (B + B2.rot_z(2 * p_ang)) / 2
    sq_mid_rad = Vec(sq_mid[0], sq_mid[1], 0).mag()
    A_rad = A[0]
    tri_ht2 = 3 * edge / 4 - (A_rad - sq_mid_rad)**2
    if tri_ht2 < -epsilon:
        raise ValueError(
            'Not possible to calculate upper equilateral triangle')

    A2_ht = (1 - 2 * belt_back) * sqrt(tri_ht2) + B[2]
    A2 = Vec(A_rad, 0, A2_ht).rot_z(p_ang)

    mid_B_B1 = Vec(B[0], 0, B[2])
    ax = C - mid_B_B1
    rot = Mat.rot_axis_ang(ax, math.pi)
    pt = A - mid_B_B1
    C2 = rot * pt + mid_B_B1

    return A, B, B2, C, A2, C2
示例#4
0
def make_equ_antiherm(pgon, dih_ang):
    """Make a hermaphrodite with equilateral triangles"""
    N = pgon.N
    a = pgon.angle()/2
    R = pgon.circumradius()
    tri_alt = sqrt(3)/2
    tri_ht = tri_alt * sin(dih_ang)
    R2 = pgon.inradius() + tri_alt * cos(dih_ang)

    points = [Vec(0, 0, 0)]*(2*N+1)
    for i in range(N):
        points[i] = Vec(R*cos(2*i*a), R*sin(2*i*a), 0)
        points[i+N] = Vec(R2*cos(2*i*a+a), R2*sin(2*i*a+a), tri_ht)

    A = sqrt(points[0][0]**2 + points[0][1]**2)
    mid = anti_lib.centroid([points[N], points[2*N-1]])
    B = sqrt(mid[0]**2 + mid[1]**2)
    z_diff = mid[2] - points[0][2]
    points[2*N][2] = A * z_diff / (A - B)

    faces = []
    faces.append([i for i in range(N)])      # bottom

    for i in range(N):
        faces.append([i, (i + 1) % N, N + i])
        faces.append([N + i, 2*N, N + (i + 1) % N, (i + 1) % N])

    return points, faces
示例#5
0
def calculate_belt_points(pgon, model_type):
    ang = pgon.angle() / 2
    tan_a = math.sin(ang) / (math.cos(ang) + phi)  # half of unit edge
    P = Vec(1 / (2 * tan_a), -1 / 2, 0)
    tan_b = math.sin(ang) / (math.cos(ang) + 1 / phi)  # half of phi edge
    Q = Vec(phi / (2 * tan_b), -phi / 2, 0)
    bar_ht = math.cos(math.pi / 10)  # planar height of pentagon "bar"
    diff_r = P[0] - Q[0]
    try:
        ht0 = math.sqrt(bar_ht**2 - diff_r**2) / 2
    except:
        raise ValueError('model is not constructible')

    P[2] = -ht0
    Q[2] = ht0
    pent_ht = math.sqrt(5 + 2 * math.sqrt(5)) / 2  # planar height of pentagon
    x_cap = P[0] - pent_ht * (diff_r / bar_ht)
    z_cap = -ht0 + pent_ht * ((2 * ht0) / bar_ht)
    R = Vec(x_cap, 0, z_cap)
    S = Vec(x_cap * math.cos(ang), x_cap * math.sin(ang), -z_cap)
    cap_inrad = x_cap * math.cos(ang)
    apex_ht = 0
    if model_type == 'a':
        try:
            apex_ht = z_cap + cap_inrad * (z_cap + P[2]) / (P[0] - cap_inrad)
        except:
            raise ValueError('could not calculate apex height')
    A = Vec(0, 0, apex_ht)
    return [P, Q, R, S, A]
示例#6
0
def j89_get_principal_verts(pgon, ang, flags):
    bad = flags.strip('ABC')
    if bad:
        raise ValueError('Unrecognised form flag(s) \''+bad+'\'')
    ridge_down = 'A' in flags
    ridge2_down = 'B' in flags
    belt_back = 'C' in flags
    pgon.N *= 2
    # p_ang2 = pgon2.angle()
    A, B, B2, C = get_pstt_cap_verts(pgon, ang, ridge_down)
    pgon.N //= 2

    p_ang = pgon.angle()/2
    A2_rad = pgon.circumradius(edge)  # circumradius of top polygon
    sq_mid = (B + B2) / 2
    sq_mid_rad = Vec(sq_mid[0], sq_mid[1], 0).mag()
    tri_ht2 = 3/4 - (A2_rad - sq_mid_rad)**2
    if tri_ht2 < -epsilon:
        raise ValueError(
            'Not possible to calculate upper equilateral triangle')

    A2_ht = (1 - 2*belt_back)*sqrt(tri_ht2) + B[2]
    A2 = Vec(A2_rad, 0, A2_ht).rot_z(p_ang/2)

    A2_minus1 = A2.rot_z(-2*p_ang)
    B2_minus1 = B2.rot_z(-2*p_ang)
    C2 = get_three_line_vert(A2_minus1, A2, B2_minus1, B, ridge2_down)
    return [pt.rot_z(p_ang/2) for pt in [A, B, B2, C, A2, C2]]
示例#7
0
def get_default_angle(pgon, radius_top, radius_bottom, height):
    a = pgon.angle() / 2
    P1 = Vec(radius_top, 0, height)
    Q1 = Vec(radius_bottom * cos(a), radius_bottom * sin(a), 0.0)
    Q2 = Vec(radius_bottom * cos(-a), radius_bottom * sin(-a), 0.0)
    v0 = (Q1 - P1).unit()
    v1 = (Q2 - P1).unit()
    cos_a = anti_lib.safe_for_trig(Vec.dot(v0, v1))
    ang = acos(cos_a)
    return pi - ang
示例#8
0
def read_coords():
    points = []
    while 1:
        line = sys.stdin.readline()
        if line == '\n':
            continue

        if line == "":
            break

        m = re.search('^ *([^ ,]+) *,? *([^ ,]+) *,? *([^ ,\n]+) *$', line)
        if not m:
            sys.stderr.write(
                'error: did not find x, y and z values in following '
                'line (1):\n')
            sys.stderr.write(line)
            sys.exit(1)
        else:
            try:
                points.append(
                    Vec(*[float(m.group(i)) for i in range(1, 3 + 1)]))
            except:
                sys.stderr.write(
                    'error: did not find x, y and z values in following '
                    'linei (2):\n')
                sys.stderr.write(line)
                sys.exit(1)

    return points
示例#9
0
def main():
    """Entry point"""
    parser = argparse.ArgumentParser(description=__doc__)

    parser.add_argument('num_balls_on_ring',
                        help='Number of balls on each ring',
                        type=int,
                        nargs='?',
                        default=10)
    parser.add_argument('-o',
                        '--outfile',
                        help='output file name (default: standard output)',
                        type=argparse.FileType('w'),
                        default=sys.stdout)

    args = parser.parse_args()

    ring_centres = read_coords()
    if not len(ring_centres):
        parser.error('no coordinates in input')

    R = ring_centres[0].mag()
    dist = find_minimum_separation(ring_centres)
    a = math.asin(dist / (2 * R))
    ball_points, ball_ang = make_ring(R, args.num_balls_on_ring, a)
    print('ball radius = %.14f' % (2 * R * math.sin(ball_ang / 2)),
          file=sys.stderr)

    out = anti_lib.OffFile(args.outfile)
    out.print_header(len(ring_centres) * len(ball_points), 0)
    for cent in ring_centres:
        mat = Mat.rot_from_to(Vec(0, 0, 1), cent)
        out.print_verts([mat * p for p in ball_points])
示例#10
0
def touching_spheres(R, p0, r0, p1, r1, p2, r2):
    # Add R to the radii, the intersection points of the three sheres are
    # the centres of the touching ball(s)
    r0 += R
    r1 += R
    r2 += R

    # find centre of circle nd readius where first two spheres intersect
    pc1, rc1 = sphere_intersection(p0, r0, p1, r1)
    if pc1 is None:
        return None, None

    # the second circle is defined by th plane of first circle intersecting
    # the third sphere
    # find centre of second circle
    p2_pc1 = pc1 - p2
    p0_p1 = p1 - p0
    unit_p0_p1 = p0_p1.unit()
    len_p2_pc2 = Vec.dot(p2_pc1, unit_p0_p1)

    # is circle outside of third sphere
    if not (r2 > len_p2_pc2 > -r2):
        return None, None

    p2_pc2 = unit_p0_p1 * len_p2_pc2
    pc2 = p2 + p2_pc2

    rc2 = math.sqrt(r2**2 - len_p2_pc2**2)

    # find pt - intersection of plane of sphere centres and line joining
    # touching spheres, and R distance pt to a touching sphere centre.
    pt, R = sphere_intersection(pc1, rc1, pc2, rc2)
    if pt is None:
        return None, None

    # make a vector length R perpendicular to the plane of the sphere centres
    R_norm = Vec.cross(p1 - p0, p2 - p0)
    len_R_norm = R_norm.mag()
    if not len_R_norm:
        return None, None

    R_norm = R_norm * R/len_R_norm

    return pt + R_norm, pt - R_norm
示例#11
0
def get_pstt_cap_verts(pgon, ang, ridge_down):
    p_ang = pgon.angle()/2
    rad = pgon.circumradius(edge)

    # Polygon vertex
    A = Vec(rad, 0, 0)

    # First and last square vertices
    B = Vec(rad + edge*cos(ang)*cos(p_ang),
            edge*cos(ang)*sin(p_ang),
            edge*sin(ang))

    B2 = Vec(B[0], -B[1], B[2]).rot_z(2*p_ang)

    A_minus1 = A.rot_z(-2*p_ang)
    B2_minus1 = B2.rot_z(-4*p_ang)
    C = get_three_line_vert(A, A_minus1, B, B2_minus1, ridge_down)

    return A, B, B2, C
示例#12
0
def get_default_angle(pgon, radius_top, radius_bottom, height):
    a = pgon.angle()/2
    P1 = Vec(radius_top, 0, height)
    Q1 = Vec(radius_bottom * cos(a), radius_bottom * sin(a), 0.0)
    Q2 = Vec(radius_bottom * cos(-a), radius_bottom * sin(-a), 0.0)
    v0 = (Q1-P1).unit()
    v1 = (Q2-P1).unit()
    cos_a = anti_lib.safe_for_trig(Vec.dot(v0, v1))
    ang = acos(cos_a)
    return pi-ang
示例#13
0
def make_frame(frame_elems, axis_angle, rad, num_segs):
    points = []
    faces = []
    if frame_elems:
        v0 = Vec(0, 0, -1)
        v1 = Vec(math.sin(axis_angle), 0, -math.cos(axis_angle))
        if 'r' in frame_elems:
            ps, fs = make_arc(v0, v1, num_segs, 0)
            points += ps
            faces += fs
        if 'a' in frame_elems:
            num_pts = len(points)
            points += [v0, -v0, v1, -v1]
            faces += [[num_pts + 0, num_pts + 1], [num_pts + 2, num_pts + 3]]

        points = [rad * p for p in points]
        faces += [[i] for i in range(len(points))]

    return points, faces
示例#14
0
def calc_polygons2(pgon0, r0, ang, pgon1, r1, angle_between_axes):
    # rotate initial vertex of polygon0 by ang. Common
    # point lies on a line through vertex in the direction
    # of axis0 (z-axis). Common vertex lies on a cylinder
    # radius r1 with axis on axis1.

    # rotate model so axis1 is on z-axis, to simplify the
    # calculation of the intersection
    rot = Mat.rot_axis_ang(Vec(0, 1, 0), angle_between_axes)

    # initial vertex turned on axis
    V = Vec(r0, 0, 0).rot_z(ang)
    # vertex in rotated model
    Q = rot * V
    # direction of line in rotated model
    u = rot * Vec(0, 0, 1)

    # equation of line is P = Q + tu for components x y z and parameter t
    # equation of cylinder at z=0 is x^2 + y^2 = r1^2
    a = u[0]**2 + u[1]**2
    b = 2 * (Q[0] * u[0] + Q[1] * u[1])
    c = Q[0]**2 + Q[1]**2 - r1**2

    disc = b**2 - 4 * a * c
    if disc < -epsilon:
        raise Exception("model is not geometrically constructible")
    elif disc < 0:
        disc = 0

    t = (-b - math.sqrt(disc)) / (2 * a)  # negative gives most distant point

    P = V + Vec(0, 0, t)  # the common point

    points = []
    points += [P.rot_z(i * pgon0.angle()) for i in range(pgon0.N)]
    faces = [[i for i in range(pgon0.N)]]

    Q = rot * P
    rot_inv = Mat.rot_axis_ang(Vec(0, 1, 0), -angle_between_axes)
    points += [rot_inv * Q.rot_z(i * pgon1.angle()) for i in range(pgon1.N)]
    faces += [[i + pgon0.N for i in range(pgon1.N)]]

    return points, faces
示例#15
0
def bifrustum_model(pgon, arg_type):
    """Construct bifrustum model with golden trapziums"""
    N = pgon.N
    ang = pgon.angle() / 2
    R0 = phi / (2 * sin(ang))
    R1 = 1 / (2 * sin(ang))
    ht = sqrt(phi**2 - (R0 - R1)**2)
    points = []
    points += [Vec(R0, 0, ht).rot_z(2 * i * ang) for i in range(N)]
    points += [Vec(R1, 0, 0).rot_z(2 * i * ang) for i in range(N)]
    points += [Vec(R0, 0, -ht).rot_z(2 * i * ang) for i in range(N)]

    faces = []
    faces.append([i for i in range(N)])
    faces.append([i + 2 * N for i in range(N)])
    for i in range(N):
        faces.append([i, (i + 1) % N, N + (i + 1) % N, N + i])
        faces.append([N + i, N + (i + 1) % N, 2 * N + (i + 1) % N, 2 * N + i])
    return points, faces
示例#16
0
def calc_polygons(pgon0, pgon1, ang, ratio, angle_between_axes):
    # rotate initial vertex of polygon0 by ang. Common
    # point lies on a line through vertex in the direction
    # of axis0 (z-axis). Common vertex lies on a cylinder
    # radius r1 with axis on axis1.

    # rotate model so axis1 is on z-axis, to simplify the
    # calculation of the intersection
    rot = Mat.rot_axis_ang(Vec(0, 1, 0), angle_between_axes)

    # initial vertex turned on axis
    V = Vec(pgon0.circumradius(), 0, 0).rot_z(ang)
    # vertex in rotated model
    Q = rot * V
    # direction of line in rotated model
    u = rot * Vec(0, 0, 1)

    # equation of line is P = Q + tu for components x y z and parameter t
    # equation of cylinder at z=0 is x^2 + y^2 = r1^2
    a = u[0]**2 + u[1]**2
    b = 2 * (Q[0] * u[0] + Q[1] * u[1])
    c = Q[0]**2 + Q[1]**2 - (pgon1.circumradius() * ratio)**2

    disc = b**2 - 4 * a * c
    if disc < -epsilon:
        raise Exception("model is not geometrically constructible")
    elif disc < 0:
        disc = 0

    t = (-b - math.sqrt(disc)) / (2 * a)

    P = V + Vec(0, 0, t)  # the common point

    points = pgon0.get_points(P)
    faces = pgon0.get_faces()

    Q = rot * P
    rot_inv = Mat.rot_axis_ang(Vec(0, 1, 0), -angle_between_axes)
    points += [rot_inv * p for p in pgon1.get_points(Q)]
    faces += pgon1.get_faces(pgon0.N * pgon0.parts)

    return points, faces
示例#17
0
def get_three_line_vert(A0, A1, B0, B1, ridge_down):
    A_edge = A1 - A0
    B_edge = B0 - A0
    cos_gamma = Vec.dot(A_edge.unit(), B_edge.unit())
    if abs(cos_gamma) > 1:
        cos_gamma /= abs(cos_gamma)
    gamma = acos(cos_gamma)
    d = (edge / 2) * tan(gamma / 2)
    h2 = 3 * edge / 4 - d**2
    if h2 < -epsilon:
        raise ValueError(
            'Not possible to calculate a three-line-fill triangle')
    h = (1 - 2 * ridge_down) * sqrt(h2)

    A_mid = (A0 + A1) / 2
    B_mid = (B0 + B1) / 2
    mid_dir = (B_mid - A_mid).unit()
    norm = Vec.cross(B_edge, A_edge).unit()
    P_from_A_mid = mid_dir * d + norm * h
    return A_mid + P_from_A_mid
示例#18
0
def make_arc(v0, v1, num_segs, start_idx):
    axis = Vec.cross(v0, v1).unit()
    ang = anti_lib.angle_around_axis(v0, v1, axis)
    points = [v0]
    faces = []
    mat = Mat.rot_axis_ang(axis, ang/num_segs)
    for i in range(num_segs):
        # accumulated error
        points.append(mat * points[-1])
        faces.append([start_idx + i, start_idx + i+1])
    return points, faces
示例#19
0
def get_three_line_vert(A0, A1, B0, B1, ridge_down):
    A_edge = A1 - A0
    B_edge = B0 - A0
    cos_gamma = Vec.dot(A_edge.unit(), B_edge.unit())
    if abs(cos_gamma) > 1:
        cos_gamma /= abs(cos_gamma)
    gamma = acos(cos_gamma)
    d = (edge/2)*tan(gamma/2)
    h2 = 3*edge/4 - d**2
    if h2 < -epsilon:
        raise ValueError(
            'Not possible to calculate a three-line-fill triangle')
    h = (1 - 2*ridge_down) * sqrt(h2)

    A_mid = (A0 + A1) / 2
    B_mid = (B0 + B1) / 2
    mid_dir = (B_mid - A_mid).unit()
    norm = Vec.cross(B_edge, A_edge).unit()
    P_from_A_mid = mid_dir*d + norm*h
    return A_mid + P_from_A_mid
示例#20
0
def antiprism_model(pgon, arg_type):
    """Construct antiprism model with golden trapziums"""
    N = pgon.N
    ang = pgon.angle() / 2
    P = tri_antiprism_pt(pgon, gold_trap_diag, 1, phi)
    Q = Vec(P[0], -P[1], -P[2])
    points = []
    for i in range(N):
        points.append(P.rot_z(2 * i * ang))
    for i in range(N):
        points.append(Q.rot_z(2 * i * ang))
    R = points[N - 1] + (P - Q) * phi
    for i in range(N):
        points.append(R.rot_z(2 * i * ang))
    S = Vec(R[0], -R[1], -R[2])
    for i in range(N):
        points.append(S.rot_z(2 * i * ang))

    faces = []
    faces.append([2 * N + i for i in range(N)])
    faces.append([3 * N + i for i in range(N)])
    for i in range(N):
        faces.append([i, 2 * N + i, 2 * N + ((i + 1) % N)])
        faces.append([N + i, 3 * N + i, 3 * N + ((i - 1) % N)])
        faces.append(
            [i, 2 * N + ((i + 1) % N), ((i + 1) % N), N + ((i + 1) % N)])
        faces.append([i, N + ((i + 1) % N), 3 * N + i, N + i])
    return points, faces
示例#21
0
def make_frame(frame_elems, pgon, axis_angle, num_segs):
    points = []
    faces = []
    if frame_elems:
        v0 = Vec(0, 0, 1)
        v1 = Vec(-math.sin(axis_angle), 0, math.cos(axis_angle))
        v2 = v1.rot_z(pgon.angle()/2)
        v2[2] *= -1
        if 'r' in frame_elems:
            ps, fs = make_arc(v0, v1, num_segs, 0)
            points += ps
            faces += fs
            ps, fs = make_arc(v1, v2, num_segs, num_segs+1)
            points += ps
            faces += fs
        if 'a' in frame_elems:
            faces += [[len(points)+i, len(points)+i+1] for i in range(0, 6, 2)]
            points += [v0, -v0, v1, -v1, v2, -v2]

        rad = calc_polygons(pgon, 0, axis_angle, -1)[0][0].mag()
        points = [rad * p for p in points]
        faces += [[i] for i in range(len(points))]

    return points, faces
示例#22
0
def tri_antiprism_pt(pgon, a, b, c):
    """Return construct point for antiprism model"""
    s = (a + b + c) / 2
    alt = 2 * math.sqrt(s * (s - a) * (s - b) *
                        (s - c)) / a  # planar height of A
    ang = pgon.angle() / 2
    R = pgon.circumradius(a)  # polygon circumradius (side a)
    r = pgon.inradius(a)  # polygon inradius (side a)
    a0 = math.sqrt(b**2 - alt**2)  # from C to perpendicular from A
    y = a0 - a / 2
    x = math.sqrt(R**2 - y**2)
    alt_proj_x = x - r  # projection of alt onto x dir
    z = math.sqrt(alt**2 - alt_proj_x**2) / 2
    ang_to_A = math.atan2(y, x)  # adjust point for dih axis
    ang_off = (ang_to_A - ang) / 2
    return Vec(x, y, z).rot_z(-ang_off)
示例#23
0
def make_ring(R, N, a):
    b = ring_ball_ang(N, a)
    P = Vec(R*math.sin(a-b), 0, R*math.cos(a-b))
    return [P.rot_z(2*math.pi*i/N) for i in range(N)], b
示例#24
0
def main():
    """Entry point"""

    epilog = '''
notes:
  Depends on anti_lib.py. Use poly_kscope to repeat the model.

examples:
  Icosahedral model
  twister.py I[5,3] | poly_kscope -s I| antiview

  Twisted icosahedral model with hexagons
  twister.py I[5,3] 1 2 -a 0.5e | poly_kscope -s I| antiview

  Dihedral model with frame
  twister.py D6[6,2] 1 2 -f ra | poly_kscope -s D6 | antiview
'''

    parser = argparse.ArgumentParser(formatter_class=anti_lib.DefFormatter,
                                     description=__doc__, epilog=epilog)

    parser.add_argument(
        'symmetry_axes',
        help=']Axes given in the form: sym_type[axis1,axis2]id_no\n'
             '   sym_type: rotational symmetry group, can be T, O, I,\n'
             '       or can be D followed by an integer (e.g D5)\n'
             '   axis1,axis2: rotational order of each of the two axes\n'
             '   id_no (default: 1): integer to select between\n'
             '       non-equivalent pairs of axes having the same\n'
             '       symmetry group and rotational orders\n'
             'e.g. T[2,3], I[5,2]2, D7[7,2], D11[2,2]4\n'
             'Axis pairs are from the following\n'
             '   T: [3, 3], [3, 2], [2, 2]\n'
             '   O: [4, 4], [4, 3], [4, 2]x2, [3, 3], [3, 2]x2,\n'
             '      [2, 2]x2\n'
             '   I: [5, 5], [5, 3]x2, [5, 2]x3, [3, 3]x2, [3, 2]x4,\n'
             '      [2, 2]x4\n'
             '   Dn: [n 2], [2,2]x(n/2 rounded down)\n',
        type=read_axes,
        nargs='?',
        default='O[4,3]')
    parser.add_argument(
        'multiplier1',
        help='integer or fractional multiplier for axis 1 '
             '(default: 1). If the axis is N/D and the '
             'multiplier is n/d the polygon used will be N*n/d',
        type=read_axis_multiplier,
        nargs='?',
        default="1")
    parser.add_argument(
        'multiplier2',
        help='integer or fractional multiplier for axis 2 '
             '(default: 1). If the axis is N/D and the '
             'multiplier is n/d the polygon used will be N*n/d',
        type=read_axis_multiplier,
        nargs='?',
        default="1")
    parser.add_argument(
        '-r', '--ratio',
        help='ratio of edge lengths (default: 1.0)',
        type=float,
        default=1.0)
    parser.add_argument(
        '-a', '--angle',
        help='amount to turn polygon on axis0 in degrees '
        '(default: 0), or a value followed by \'e\', '
             'where 1.0e is half the central angle of an edge, '
             'which produces an edge-connected model (negative '
             'values may have to be specified as, e.g. -a=-1.0e), '
             'or a value followed by x, which is like e but with '
             'a half turn offset',
        type=read_turn_angle,
        default='0')
    parser.add_argument(
        '-f', '--offset',
        help='amount to offset the first polygon to avoid '
             'coplanarity with the second polygon, for example '
             '0.0001 (default: 0.0)',
        type=float,
        default=0.0)
    parser.add_argument(
        '-F', '--frame',
        help='include frame elements in output, any from: '
             'r - rhombic tiling edges, '
             'a - rotation axes (default: no elements)',
        type=frame_type,
        default='')
    parser.add_argument(
        '-o', '--outfile',
        help='output file name (default: standard output)',
        type=argparse.FileType('w'),
        default=sys.stdout)

    args = parser.parse_args()

    axis_pair = args.symmetry_axes
    pgons = []
    for i, m in enumerate([args.multiplier1, args.multiplier2]):
        try:
            pgons.append(anti_lib.Polygon(
                axis_pair['nfolds'][i]*m.N, m.D))
        except Exception as e:
                parser.error('multiplier%d: ' % (i) + e.args[0])

    axes = axis_pair['axes']

    if args.angle[1] == 'e':     # units: half edge central angle
        turn_angle = args.angle[0] * pgons[0].angle()/2
    elif args.angle[1] == 'x':   # units: half edge central angle
        turn_angle = math.pi + args.angle[0] * pgons[0].angle()/2
    else:                        # units: degrees
        turn_angle = math.radians(args.angle[0])

    sin_angle_between_axes = Vec.cross(axes[0], axes[1]).mag()
    if abs(sin_angle_between_axes) > 1:
        sin_angle_between_axes = -1 if sin_angle_between_axes < 0 else 1
    angle_between_axes = math.asin(sin_angle_between_axes)
    if(Vec.dot(axes[0], axes[1]) > 0):
        axes[1] *= -1

    try:
        (points, faces) = calc_polygons(
            pgons[0], pgons[1], turn_angle, args.ratio, angle_between_axes)
    except Exception as e:
        parser.error(e.args[0])

    if args.offset:
        for i in range(len(faces[0])):
            points[i][2] += args.offset

    frame_rad = calc_polygons(pgons[0], pgons[1], 0, args.ratio,
                              angle_between_axes)[0][0].mag()
    frame_points, frame_faces = make_frame(args.frame, angle_between_axes,
                                           frame_rad, 10)

    rot = Mat.rot_from_to2(Vec(0, 0, 1), Vec(1, 0, 0), axes[0], axes[1])
    all_points = [rot * point for point in points+frame_points]

    out = anti_lib.OffFile(args.outfile)
    out.print_header(len(all_points), len(faces)+len(frame_faces))
    out.print_verts(all_points)
    for i in range(pgons[0].parts+pgons[1].parts):
        out.print_face(faces[i], 0, int(i < pgons[0].parts))
    out.print_faces(frame_faces, len(points), 2)