The game plan: drop a small object through a laser beam that shines on a photodiode, thus causing an electrical signal that triggers various flashes and cameras and so forth and so on. This fixture holds the laser and photodiode in the proper orientation, with enough stability that you (well, I) can worry about other things:

It’s mounted on the blade of a dirt-cheap 2 foot machinist’s square clamped to the bench which will probably get a few holes drilled in its baseplate for more permanent mounting.
The solid model looks about like you’d expect:

There’s a small hole in the back for an 8-32 setscrew that locks it to the blade; the fit turned out snug enough to render the screw superfluous. I added those two square blocks with the holes after I taped the wires to the one in the picture.
The two semicircular (well, half-octagonal) trenches have slightly different diameters to suit the heatshrink tubing around the photodiode (a.k.a., IR LED) and brass laser housing. A dab of fabric adhesive holds the tubes in place, in addition to the Gorilla Tape on the ends.
The laser came focused at infinity, of course. Unscrewing the lens almost all the way put the focus about 3/4 of the way across the ring; call it 40 mm. The beam is rectangular, about 1×2 mm, at the center of the ring, and I rotated the body to make the short axis vertical; that’s good enough for my purposes.
The cable came from a pair of cheap earbuds with separate Left/Right pairs all the way from the plug.
The model builds in one piece, of course, and pops off the platform ready to use:

If you were doing this for an analytic project, you’d want a marker for the beam centerline on the vertical scale, but that’s in the nature of fine tuning. As it stands, the beam sits 8 mm above the base and flush with the top surface of the ring; if that were 10 mm, it’d be easier to remember.
The OpenSCAD source code has a few tweaks and improvements:
// Laser and LED-photodiode break-beam sensor // Ed Nisley - KE4ZNU - March 2014 Layout = "Show"; // Build Show Ring Mount Guide //- Extrusion parameters must match reality! // Print with 2 shells and 3 solid layers ThreadThick = 0.20; ThreadWidth = 0.40; HoleWindage = 0.2; // extra clearance Protrusion = 0.1; // make holes end cleanly AlignPinOD = 1.70; // assembly alignment pins: filament dia inch = 25.4; function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit); //---------------------- // Dimensions LaserOD = 6.0; // brass focus tube LaserLength = 20.0; // ... wire clearance SensorOD = 6.5; // including light shield SensorLength = 20.0; // ... wire clearance RingSize = [50.0,70.0,8.0,8*4]; // support ring dimensions RING_ID = 0; RING_OD = 1; RING_THICK = 2; RING_SIDES = 3; StrutWidth = 2.5; // strut supporting this thing StrutLength = 26.5; StrutBlock = [10.0,35.0,20.0]; // block around the clearance slot BLOCK_WIDTH = 0; BLOCK_LENGTH = 1; BLOCK_HEIGHT = 2; StrutScrewTap = 2.7; // 6-32 SHCS GuideID = 4.0; // guide for cables GuideOD = 3*GuideID; BuildSpace = 3.0; // spacing between objects on platform //---------------------- // Useful routines module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2); FixDia = Dia / cos(180/Sides); cylinder(r=(FixDia + HoleWindage)/2, h=Height, $fn=Sides); } module ShowPegGrid(Space = 10.0,Size = 1.0) { RangeX = floor(100 / Space); RangeY = floor(125 / Space); for (x=[-RangeX:RangeX]) for (y=[-RangeY:RangeY]) translate([x*Space,y*Space,Size/2]) %cube(Size,center=true); } module Ring() { difference() { union() { rotate(180/RingSize[RING_SIDES]) cylinder(d=RingSize[RING_OD],h=RingSize[RING_THICK], $fn=RingSize[RING_SIDES]); translate([-LaserOD,(-LaserLength - RingSize[RING_ID]/2),0]) cube([2*LaserOD,LaserLength,RingSize[RING_THICK]],center=false); translate([-SensorOD,(-0*SensorLength + RingSize[RING_ID]/2),0]) cube([2*SensorOD,SensorLength,RingSize[RING_THICK]],center=false); } rotate(180/RingSize[RING_SIDES]) translate([0,0,-Protrusion]) cylinder(d=RingSize[RING_ID],h=(RingSize[RING_THICK] + 2*Protrusion), $fn=RingSize[RING_SIDES]); translate([0,0,RingSize[RING_THICK]]) rotate([90,0,0]) rotate(180/8) PolyCyl(LaserOD,3*LaserLength,8); translate([0,0,RingSize[RING_THICK]]) rotate([-90,0,0]) rotate(180/8) PolyCyl(SensorOD,3*SensorLength,8); } } module Mount() { translate([0,0,StrutBlock[2]/2]) difference() { cube(StrutBlock,center=true); cube([StrutWidth,StrutLength,2*StrutBlock[2]],center=true); translate([0,-StrutLength/3,0]) rotate([90,0,0]) PolyCyl(StrutScrewTap,StrutLength/2,6); } } module Guide() { difference() { translate([0,0,RingSize[RING_THICK]/2]) cube([GuideOD,GuideOD,RingSize[RING_THICK]],center=true); translate([0,0,-Protrusion]) rotate(180/8) PolyCyl(GuideID,(RingSize[RING_THICK] + 2*Protrusion),8); } } module Assembly() { Ring(); translate([(RingSize[RING_OD]/2 + StrutBlock[BLOCK_LENGTH]/2 - (StrutBlock[BLOCK_LENGTH] - StrutLength)/2) + Protrusion,0,0]) rotate(90) Mount(); for (i=[-1,1]) translate([(RingSize[RING_OD]/2 + GuideID/2), i*(StrutBlock[BLOCK_WIDTH]/2 + GuideID), 0]) Guide(); } //- Build it ShowPegGrid(); if (Layout == "Ring") { Ring(); } if (Layout == "Mount") { Mount(); } if (Layout == "Guide") { Guide(); } if (Layout == "Show") { Assembly(); } if (Layout == "Build") { translate([-5/2,-5/2,0]) cube(5); }