Ejemplo n.º 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)
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
def rot_reflect_pair(points, pgon, d, rev=False):
    mat0 = Mat.rot_axis_ang(Vec(0, 0, 1), (d+0.5)*pgon.angle())
    pts = [mat0 * Vec(p[0], p[1], -p[2]) for p in points]
    mat1 = Mat.rot_axis_ang(Vec(0, 0, 1), d*pgon.angle())
    if rev:
        return [mat1 * p for p in points]+pts
    else:
        return pts + [mat1 * p for p in points]
Ejemplo n.º 4
0
def rot_reflect_pair(points, pgon, d, rev=False):
    mat0 = Mat.rot_axis_ang(Vec(0, 0, 1), (d + 0.5) * pgon.angle())
    pts = [mat0 * Vec(p[0], p[1], -p[2]) for p in points]
    mat1 = Mat.rot_axis_ang(Vec(0, 0, 1), d * pgon.angle())
    if rev:
        return [mat1 * p for p in points] + pts
    else:
        return pts + [mat1 * p for p in points]
Ejemplo n.º 5
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
Ejemplo n.º 6
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
Ejemplo n.º 7
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])
Ejemplo n.º 8
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])
Ejemplo n.º 9
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
Ejemplo n.º 10
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
Ejemplo n.º 11
0
def calc_polygons(pgon, ang, angle_between_axes, sign_flag=1):
    # rotate initial vertex of first polygon by ang. Common
    # point lies on a line through vertex in the direction
    # of first axis (z-axis). Common point lies on a cylinder
    # with the circumradius for radius and second axis for axis.

    # 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)

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

    # 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 - R**2

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

    # The sign flag, which changes for the range 90 to 270 degrees, allows
    # the model to reverse, otherwise the model breaks apart in this range.
    t = (-b + sign_flag * math.sqrt(disc)) / (2 * a)

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

    points = pgon.get_points(P)
    faces = pgon.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 pgon.get_points(Q)]
    faces += pgon.get_faces(pgon.N * pgon.parts)

    return points, faces
Ejemplo n.º 12
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
Ejemplo n.º 13
0
def calc_polygons(pgon, ang, angle_between_axes, sign_flag=1):
    # rotate initial vertex of first polygon by ang. Common
    # point lies on a line through vertex in the direction
    # of first axis (z-axis). Common point lies on a cylinder
    # with the circumradius for radius and second axis for axis.

    # 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)

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

    # 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 - R**2

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

    # The sign flag, which changes for the range 90 to 270 degrees, allows
    # the model to reverse, otherwise the model breaks apart in this range.
    t = (-b + sign_flag*math.sqrt(disc))/(2*a)

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

    points = pgon.get_points(P)
    faces = pgon.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 pgon.get_points(Q)]
    faces += pgon.get_faces(pgon.N*pgon.parts)

    return points, faces
Ejemplo n.º 14
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
Ejemplo n.º 15
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
Ejemplo n.º 16
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
Ejemplo n.º 17
0
def get_base_points(pgon, centres=0, twist=False):
    N = pgon.N
    D = pgon.D
    a = pgon.angle()/2
    points = [Vec(0, 0, 1), Vec(0, 0, -1)]  # poles
    A = a
    B = math.pi/2 * (1 - D/N)
    cos_lat = 1/(math.tan(A) * math.tan(B))
    sin_lat = math.sqrt(1 - cos_lat**2)
    p_north = Vec(sin_lat, 0, cos_lat)
    p_south = Vec(sin_lat, 0, -cos_lat)
    for i in range(N):
        points.append(p_north.rot_z(i*2*a))
    for i in range(N):
        points.append(p_south.rot_z(i*2*a+a))
    if centres:
        cos_cent_lat = math.cos(B)/math.sin(A)
        sin_cent_lat = math.sqrt(1 - cos_cent_lat**2)
        cent_north = Vec(sin_cent_lat, 0, cos_cent_lat)
        cent_south = Vec(sin_cent_lat, 0, -cos_cent_lat)
        for i in range(N):
            points.append(cent_north.rot_z(i*2*a+a) * centres)
        for i in range(N):
            points.append(cent_south.rot_z(i*2*a) * centres)

    rhombi = []
    for i in range(N):
        rhombi.append([2 + i, 0, 2 + ((i+1) % N), 2 + N + i])
    for i in range(N):
        rhombi.append([2 + N + i, 1, 2 + N + ((i-1) % N), 2 + i])

    if twist:
        hx = [0, 2, 2 + N, 1, 2 + N+N//2, 2 + N-N//2]
        axis = points[hx[0]] + points[hx[2]] + points[hx[4]]
        rot = Mat.rot_axis_ang(axis.unit(), 2*math.pi/3)
        for i in list(range(hx[5]+1, 2 + N)) + list(range(hx[4]+1, 2 + 2*N)):
            points[i] = rot * points[i]
        if centres:
            for r in list(range(N-N//2, N+1)) + list(range(2*N-N//2, 2*N)):
                points[2 + 2*N + r] = rot * points[2 + 2*N + r]
        for r in list(range(N-N//2, N+1)) + list(range(2*N-N//2, 2*N)):
            for i in range(4):
                for idx, p_idx in enumerate(hx):
                    if p_idx == rhombi[r][i]:
                        rhombi[r][i] = hx[(idx+2) % 6]
                        break

    return points, rhombi
Ejemplo n.º 18
0
def make_hexagonal_tiling(freq):
    grads = freq + 1
    points = [[0, 0, 0]]
    faces = []
    for i in range(6):
        rot = Mat.rot_axis_ang(Vec(0, 0, 1), i * math.pi / 3)
        for j in range(1, grads):
            points.append(rot * Vec(j, 0, 0))

    for t in range(6):
        p1 = ((t + 1) % 6) * freq + 1
        p2 = t * freq + 1
        vec = points[p1] - points[p2]
        for i in range(1, freq):
            for j in range(0, i):
                points.append(points[t * freq + 1 + i] + vec * (j + 1))
        faces += get_tri_faces(t, freq)

    return points, faces, points[freq].mag()
Ejemplo n.º 19
0
def make_hexagonal_tiling(freq):
    grads = freq + 1
    points = [[0, 0, 0]]
    faces = []
    for i in range(6):
        rot = Mat.rot_axis_ang(Vec(0, 0, 1), i*math.pi/3)
        for j in range(1, grads):
            points.append(rot * Vec(j, 0, 0))

    for t in range(6):
        p1 = ((t+1) % 6)*freq + 1
        p2 = t*freq + 1
        vec = points[p1] - points[p2]
        for i in range(1, freq):
            for j in range(0, i):
                points.append(points[t*freq+1+i] + vec*(j+1))
        faces += get_tri_faces(t, freq)

    return points, faces, points[freq].mag()
Ejemplo n.º 20
0
def main():
    """Entry point"""
    parser = argparse.ArgumentParser(description=__doc__)

    parser.add_argument(
        'number_sides0',
        help='number of sides of polygon 0 (default: 6) '
             '(or may be a polygon fraction, e.g. 5/2)',
        type=anti_lib.read_polygon,
        nargs='?',
        default='6')
    parser.add_argument(
        'number_sides1',
        help='number of sides of polygon 0 (default: 5) '
             '(or may be a polygon fraction, e.g. 5/2)',
        type=anti_lib.read_polygon,
        nargs='?',
        default='5')
    parser.add_argument(
        '-A', '--angle_between_axes',
        help='angle between the two axes (default: 60 degs)',
        type=float,
        default=60.0)
    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(
        '-x', '--x-axis-vert',
        help='offset of vertex of side polygon to align with x-axis, with 0'
             'being the vertex attached to the axial polygon',
        type=int)
    parser.add_argument(
        '-o', '--outfile',
        help='output file name (default: standard output)',
        type=argparse.FileType('w'),
        default=sys.stdout)

    args = parser.parse_args()

    pgon0 = args.number_sides0
    pgon1 = args.number_sides1

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

    try:
        (points, faces) = calc_polygons2(
            pgon0, pgon0.circumradius(), turn_angle,
            pgon1, args.ratio*pgon1.circumradius(),
            math.radians(args.angle_between_axes))
    except Exception as e:
        parser.error(e.args[0])

    if(args.x_axis_vert is not None):
        v = pgon0.N + args.x_axis_vert % pgon1.N
        P = points[v].copy()
        transl = Mat.transl(Vec(0, 0, -P[2]))
        # for i in range(len(points)):
        #    points[i][2] -= P[2]
        rot = Mat.rot_xyz(0, 0, anti_lib.angle_around_axis(
            P, Vec(1, 0, 0),  Vec(0, 0, 1)))
        points = [rot*transl*point for point in points]

    out = anti_lib.OffFile(args.outfile)
    out.print_all(points, faces)
Ejemplo n.º 21
0
def main():
    """Entry point"""
    parser = argparse.ArgumentParser(description=__doc__)

    parser.add_argument(
        'polygon',
        help='number of sides of polygon (default: 7) '
        '(or may be a polygon fraction, e.g. 5/2)',
        type=anti_lib.read_polygon,
        nargs='?',
        default='7')
    parser.add_argument(
        'turn_angle',
        help='amount to turn polygon on axis0 in degrees '
             '(default (0.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,
        nargs='?',
        default='0')
    parser.add_argument(
        '-n', '--number-faces',
        help='number of faces in output (default: all): '
             '0 - none (frame only), '
             '2 - cap and adjoining polygon'
             '4 - two caps and two adjoining connected polygons',
        type=int,
        choices=[0, 2, 4],
        default=-1)
    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()

    pgon = args.polygon
    N = pgon.N
    D = pgon.D
    parts = pgon.parts
    # if N % 2 == 0:
    #    parser.error('polygon: %sfraction numerator must be odd' %
    #                 ('reduced ' if parts > 1 else ''))
    if D % 2 == 0:
        print(os.path.basename(__file__)+': warning: '
              'polygon: %sfraction denominator should be odd, '
              'model will only connect correctly at certain twist angles' %
              ('reduced ' if parts > 1 else ''),
              file=sys.stderr)

    if abs(N/D) < 3/2:
        parser.error('polygon: the polygon fraction cannot be less than 3/2 '
                     '(base rhombic tiling is not constructible)')

    axis_angle = math.acos(1/math.tan(
        math.pi*D/N)/math.tan(math.pi*(N-D)/(2*N)))
    if args.turn_angle[1] == 'e':     # units: half edge central angle
        turn_angle = args.turn_angle[0] * pgon.angle()/2
    elif args.turn_angle[1] == 'x':   # units: half edge central angle
        turn_angle = math.pi + args.turn_angle[0] * pgon.angle()/2
    else:                        # units: degrees
        turn_angle = math.radians(args.turn_angle[0])

    turn_angle_test_val = abs(math.fmod(abs(turn_angle), 2*math.pi) - math.pi)
    sign_flag = 1 - 2*(turn_angle_test_val > math.pi/2)
    try:
        (points, faces) = calc_polygons(pgon, turn_angle, axis_angle,
                                        sign_flag)
    except Exception as e:
        parser.error(e.args[0])

    if args.number_faces < 0:
        num_twist_faces = (2*N + 2)*parts
    else:
        num_twist_faces = args.number_faces*parts
    num_twist_points = num_twist_faces * N

    frame_points, frame_faces = make_frame(args.frame, pgon, axis_angle, 10)

    if num_twist_points + len(frame_points) == 0:
        parser.error('no output specified, use -f with -n 0 to output a frame')

    if D % 2 == 0:
        mat = Mat.rot_axis_ang(Vec(0, 0, 1), math.pi/N)
        points = [mat * p for p in points]

    out = anti_lib.OffFile(args.outfile)
    out.print_header(num_twist_points+2*N*len(frame_points),
                     num_twist_faces+2*N*len(frame_faces))
    if args.number_faces == -1:
        out.print_verts(rot_reflect_pair(points[:N*parts], pgon, 0, True))
        for i in range(N):
            out.print_verts(rot_reflect_pair(points[N*parts:], pgon, i))
    elif args.number_faces == 2:
        out.print_verts(points)
    elif args.number_faces == 4:
        out.print_verts(rot_reflect_pair(points[:N*parts], pgon, 0, True),)
        out.print_verts(rot_reflect_pair(points[N*parts:], pgon, 0))

    for i in range(N):
        out.print_verts(rot_reflect_pair(frame_points, pgon, i))

    for i in range(num_twist_faces):
        out.print_face(faces[0], i*N, (i//parts) % 2)

    for i in range(N):
        cur_num_points = num_twist_points + 2*i*len(frame_points)
        for j in [0, len(frame_points)]:
            out.print_faces(frame_faces, cur_num_points+j, col=2)
Ejemplo n.º 22
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)
Ejemplo n.º 23
0
def main():
    """Entry point"""
    parser = argparse.ArgumentParser(description=__doc__)

    parser.add_argument('number_sides0',
                        help='number of sides of polygon 0 (default: 6) '
                        '(or may be a polygon fraction, e.g. 5/2)',
                        type=anti_lib.read_polygon,
                        nargs='?',
                        default='6')
    parser.add_argument('number_sides1',
                        help='number of sides of polygon 0 (default: 5) '
                        '(or may be a polygon fraction, e.g. 5/2)',
                        type=anti_lib.read_polygon,
                        nargs='?',
                        default='5')
    parser.add_argument('-A',
                        '--angle_between_axes',
                        help='angle between the two axes (default: 60 degs)',
                        type=float,
                        default=60.0)
    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(
        '-x',
        '--x-axis-vert',
        help='offset of vertex of side polygon to align with x-axis, with 0'
        'being the vertex attached to the axial polygon',
        type=int)
    parser.add_argument('-o',
                        '--outfile',
                        help='output file name (default: standard output)',
                        type=argparse.FileType('w'),
                        default=sys.stdout)

    args = parser.parse_args()

    pgon0 = args.number_sides0
    pgon1 = args.number_sides1

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

    try:
        (points, faces) = calc_polygons2(pgon0, pgon0.circumradius(),
                                         turn_angle, pgon1,
                                         args.ratio * pgon1.circumradius(),
                                         math.radians(args.angle_between_axes))
    except Exception as e:
        parser.error(e.args[0])

    if (args.x_axis_vert is not None):
        v = pgon0.N + args.x_axis_vert % pgon1.N
        P = points[v].copy()
        transl = Mat.transl(Vec(0, 0, -P[2]))
        # for i in range(len(points)):
        #    points[i][2] -= P[2]
        rot = Mat.rot_xyz(
            0, 0, anti_lib.angle_around_axis(P, Vec(1, 0, 0), Vec(0, 0, 1)))
        points = [rot * transl * point for point in points]

    out = anti_lib.OffFile(args.outfile)
    out.print_all(points, faces)
Ejemplo n.º 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)
Ejemplo n.º 25
0
def main():
    """Entry point"""
    parser = argparse.ArgumentParser(description=__doc__)

    parser.add_argument('polygon',
                        help='number of sides of polygon (default: 7) '
                        '(or may be a polygon fraction, e.g. 5/2)',
                        type=anti_lib.read_polygon,
                        nargs='?',
                        default='7')
    parser.add_argument('turn_angle',
                        help='amount to turn polygon on axis0 in degrees '
                        '(default (0.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,
                        nargs='?',
                        default='0')
    parser.add_argument('-n',
                        '--number-faces',
                        help='number of faces in output (default: all): '
                        '0 - none (frame only), '
                        '2 - cap and adjoining polygon'
                        '4 - two caps and two adjoining connected polygons',
                        type=int,
                        choices=[0, 2, 4],
                        default=-1)
    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()

    pgon = args.polygon
    N = pgon.N
    D = pgon.D
    parts = pgon.parts
    # if N % 2 == 0:
    #    parser.error('polygon: %sfraction numerator must be odd' %
    #                 ('reduced ' if parts > 1 else ''))
    if D % 2 == 0:
        print(os.path.basename(__file__) + ': warning: '
              'polygon: %sfraction denominator should be odd, '
              'model will only connect correctly at certain twist angles' %
              ('reduced ' if parts > 1 else ''),
              file=sys.stderr)

    if abs(N / D) < 3 / 2:
        parser.error('polygon: the polygon fraction cannot be less than 3/2 '
                     '(base rhombic tiling is not constructible)')

    axis_angle = math.acos(1 / math.tan(math.pi * D / N) /
                           math.tan(math.pi * (N - D) / (2 * N)))
    if args.turn_angle[1] == 'e':  # units: half edge central angle
        turn_angle = args.turn_angle[0] * pgon.angle() / 2
    elif args.turn_angle[1] == 'x':  # units: half edge central angle
        turn_angle = math.pi + args.turn_angle[0] * pgon.angle() / 2
    else:  # units: degrees
        turn_angle = math.radians(args.turn_angle[0])

    turn_angle_test_val = abs(
        math.fmod(abs(turn_angle), 2 * math.pi) - math.pi)
    sign_flag = 1 - 2 * (turn_angle_test_val > math.pi / 2)
    try:
        (points, faces) = calc_polygons(pgon, turn_angle, axis_angle,
                                        sign_flag)
    except Exception as e:
        parser.error(e.args[0])

    if args.number_faces < 0:
        num_twist_faces = (2 * N + 2) * parts
    else:
        num_twist_faces = args.number_faces * parts
    num_twist_points = num_twist_faces * N

    frame_points, frame_faces = make_frame(args.frame, pgon, axis_angle, 10)

    if num_twist_points + len(frame_points) == 0:
        parser.error('no output specified, use -f with -n 0 to output a frame')

    if D % 2 == 0:
        mat = Mat.rot_axis_ang(Vec(0, 0, 1), math.pi / N)
        points = [mat * p for p in points]

    out = anti_lib.OffFile(args.outfile)
    out.print_header(num_twist_points + 2 * N * len(frame_points),
                     num_twist_faces + 2 * N * len(frame_faces))
    if args.number_faces == -1:
        out.print_verts(rot_reflect_pair(points[:N * parts], pgon, 0, True))
        for i in range(N):
            out.print_verts(rot_reflect_pair(points[N * parts:], pgon, i))
    elif args.number_faces == 2:
        out.print_verts(points)
    elif args.number_faces == 4:
        out.print_verts(rot_reflect_pair(points[:N * parts], pgon, 0, True), )
        out.print_verts(rot_reflect_pair(points[N * parts:], pgon, 0))

    for i in range(N):
        out.print_verts(rot_reflect_pair(frame_points, pgon, i))

    for i in range(num_twist_faces):
        out.print_face(faces[0], i * N, (i // parts) % 2)

    for i in range(N):
        cur_num_points = num_twist_points + 2 * i * len(frame_points)
        for j in [0, len(frame_points)]:
            out.print_faces(frame_faces, cur_num_points + j, col=2)