Пример #1
0
def make_lens(R1, R2, d, z_offset, material=None, external_material=None):
    """
    The resulting lens faces in direction of positive z-axis.

    The sign convention here is as in https://en.wikipedia.org/wiki/Lens#Lensmaker's_equation .
    That is:
    - R1 is curvature of lens closer to source (larger z).
    - R > 0 means center of curvature is at more negative z (farther along in path of light).
    - So for a common convex lens, R1 > 0 and R2 < 0.
    Careful: This is the opposite as above.

    The z-axis intersects first surface at z = z_offset + d/2 and the 2nd surface at z = z_offset - d/2.

    The default material is BK7.

    The lensmaker's equation:
    1/f = (n - 1)*[1/R1 - 1/R2 + (n-1)*d/(n * R1 * R2)]
    (n = ior)
    """
    # We handle defaults this way so that if None is passed we can fill in the default.
    if material is None:
        material = bk7
    if external_material is None:
        external_material = air
    center1 = z_offset + d / 2 - R1
    center2 = z_offset - d / 2 - R2
    sphere1 = Quadric.sphere(R1).untransform(translation3f(0, 0, -center1))
    sphere2 = Quadric.sphere(R2).untransform(translation3f(0, 0, -center2))
    # TODO: worry where the two surfaces meet and introduce appropriate clipping
    element1 = SubElement(sphere1, None, material=material)
    element2 = SubElement(sphere2, None, material=external_material)
    return Compound([element1, element2], comment="lens")
Пример #2
0
def standard_source(z, radius):
    """A source pointing "down" (direction of rays is (0,0,-1)) from (0,0,z) with given radius"""
    debug = False
    if debug:
        return LinearSource(radius, z)
    source_orientation = np.diag([-1, 1, -1, 1])
    source_T = translation3f(0, 0, z).dot(source_orientation)
    source = CircularSource(source_T, radius)
    return source
Пример #3
0
def make_conic(R, K, z_offset, material=None, reverse_normal=False):
    """
    See https://en.wikipedia.org/wiki/Conic_constant

    r^2 - 2Rz + (K+1)z^2 = 0

    Be careful about the sign convention for radius of curvature.  We follow the convention
    in https://en.wikipedia.org/wiki/Conic_constant but this is opposite the convention in
    https://en.wikipedia.org/wiki/Lens#Lensmaker's_equation .

    Args:
        R: radius of curvature; use R > 0 for concave "up" (direction of positive z-axis) while R < 0
         is concave "down"
        K: conic constant; should be < -1 for hyperboloids, -1 for paraboloids, > -1 for ellipses
         (including 0 for spheres).  The relationship with eccentricity e is K = -e^2 (when
         K <= 0).
        z_offset: z-coordinate where the surface intersects z-axis
        material: mostly self explanatory; None means reflector
        reverse_normal: If true, surface points in direction of negative z-axis rather than
         positive z-axis
    """
    M = np.diag([1, 1, (K + 1), 0])
    M[2, 3] = -R
    M[3, 2] = M[2, 3]
    # For either sign of R, we want the convention that gradient points up at origin.
    # That gradient is (0,0,-R).
    # When R < 0, we already have that.
    # For R > 0, we need to negate M to get that.
    if R > 0:
        M *= -1
    if reverse_normal:
        M *= -1
    quad = Quadric(M)
    geometry = quad.untransform(translation3f(0, 0, -z_offset))
    if R > 0:
        # We want to keep the top sheet.
        # TODO: Let clip_z be halfway between the two foci.
        clip_z = z_offset - 1e-6
        clip = Plane(make_bound_vector(point(0, 0, clip_z), vector(0, 0, -1)))
    else:
        clip_z = z_offset + 1e-6
        clip = Plane(make_bound_vector(point(0, 0, clip_z), vector(0, 0, 1)))
    return SubElement(geometry, clip, material=material)
Пример #4
0
 def setback(self, offset):
     """Positive offset means move sensor back (away from the direction its pointing)"""
     M = self.M.dot(translation3f(0, 0, -offset))
     return PlanarSensor(M)
Пример #5
0
def make_classical_cassegrain(focal_length, d, b, aperture_radius):
    """A classical Cassegrain scope: parabolic primary, hyperbolic secondary.

    One thing that's a little tricky is it's hard to infer the parameters from
    product descriptions (not that they're classical Cassegrains, but still...).
    Eyeballing some pictures it looks like b is typically about half of d.
    Note that d is significantly shorter than total length of the tube.

    Args:
        focal_length: focal length
        d: distance between primary and secondary (along main axis)
        b: backfocus (how far behind the primary the focal plane is)
        aperture_radius: aperture radius
        #f1: focal length of primary reflector

    Returns:
        The resulting Instrument

    """
    # Our notation follows https://ccdsh.konkoly.hu/wiki/Cassegrain_Optics
    # Some math:
    # M := secondary magnification = f / f1 where f1 is focal length of primary
    # M should also be the ratio of the distances to the two focal points from
    # the secondary.
    # Our actual focal plane is at z = -b (with back of primary at z=0).
    # The secondary is at z = d.
    # So consider a proposed f1; then one focal point is at z=f1.
    # The two distances to the foci are d+b and (f1-d), so
    # M = (d+b)/(f1-d)
    # so f1 = d + (d + b)/M, one of the formulas we see on that page.
    # This yields f1 = d + (d+b)/(f/f1) = d + f1(d+b)/f
    # (1 - (d+b)/f) f1 = d
    # f1 = d / (1 - (d+b)/f)
    # Thinking in the (r,z) plane, we want the hyperbola through the point
    # (0,d) with foci (0,f1) and (0,-b)
    # The hyperbola is centered at z=(f1 - b)/2 =: s (our notation)
    # Let z' = z - s; switching to the (r,z') plane, the hyperbola goes through
    # (0, d-s) with foci (0, f1 - s) and (0, -(f1-s)).
    # Let c = f1 - s, a = d - s
    # We'll have z'^2 / a^2 - r^2/beta^2 = 1
    # Write this as r^2/beta^2 - z'^2/a^2 + 1 = 0 (so gradient points in direction
    # we want).
    # (I'm using beta rather than the b you'll see in a textbook because b is
    # already in scope.)
    # The relationship is c^2 = a^2 + beta^2
    # So, beta^2 = c^2 - a^2
    f = focal_length
    f1 = d / (1 - (d + b) / f)
    # M = f / f1
    # f2 = -(d + b) / (M - 1) # By convention, we'll say the hyperboloid has negative focal length.
    s = (f1 - b) / 2
    c = f1 - s
    c_alt = s + b
    print(f"d={d}, b={b}, s={s}, f1={f1}, c={c}, c_alt={c_alt}")
    assert np.isclose(c, c_alt)
    a = d - s
    beta2 = c**2 - a**2

    translated_secondary_M = np.diag([1 / beta2, 1 / beta2, -1 / (a**2), 1])
    translated_secondary_geometry = Quadric(translated_secondary_M)
    secondary_geometry = translated_secondary_geometry.untransform(
        translation3f(0, 0, -s))
    secondary = SubElement(secondary_geometry, clip=None)
    primary = make_paraboloid(f1)
    aperture0 = CircularAperture(point(0, 0, d),
                                 vector(0, 0, -aperture_radius))
    # Reflector is z = r^2 / (4 focal length), so at edge,
    # we have:
    z_reflector_edge = aperture_radius**2 / (4 * f1)
    aperture1 = CircularAperture(point(0, 0, z_reflector_edge),
                                 vector(0, 0, -aperture_radius))

    #source = standard_source(focal_length, aperture_radius)
    source = standard_source(d, aperture_radius)
    elements = Compound([aperture0, aperture1, primary, secondary])
    sensor = PlanarSensor.of_q_x_y(point(0, 0, -b), vector(1, 0, 0),
                                   vector(0, 1, 0))
    return Instrument(source, elements, sensor)