The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

The New Hotness

  • Browning Hi-Power Magazine Capacity Reduction: Blocks

    After a bit of trial fitting and tweaking, just three parameters cover the variations for the magazines in hand:

    • Offset of screw from front-to-back center
    • Height of spring retaining crimp
    • Distance between screw and crimp

    Collecting those numbers in a single array, with constants to select the entries, makes some sense:

    //BlockData =  [-0.5, 1.5, 11.5];	// Browning OEM
    BlockData = [-1.5, 2.0, 9.0];		// Generic 1
    
    SCREWOFFSET = 0;
    CRIMPHEIGHT = 1;
    CRIMPDISTANCE = 2;
    

    Although commenting out an undesired variable isn’t fashionable, OpenSCAD doesn’t have a practical mechanism to set specific values based on a control variable:

    • if-then-else deals with geometric objects
    • (boolean)?when_true:when_false (the ternary operator) doesn’t scale well

    You could, of course, depend on OpenSCAD’s behavior of using the last (in syntactic order) instance of a “variable”, but IMHO that’s like depending on semantic whitespace.

    In any event, the rest of the block builds itself around those three values by recomputing all of its dimensions.

    The Browning OEM block looks like this:

    Browning Hi-Power Magazine Block - solid model - BHP OEM
    Browning Hi-Power Magazine Block – solid model – BHP OEM

    The Generic floorplate has a much larger spring retaining crimp, so the block has far more overhang:

    Browning Hi-Power Magazine Block - solid model - Generic 1
    Browning Hi-Power Magazine Block – solid model – Generic 1

    As before, the yellow widgets are built-in support structures separated from the main object by one thread thickness and width. That seems to maintain good vertical tolerance and allow easy removal; the structures snap free with minimal force. A closeup look shows the gaps:

    Browning Hi-Power Magazine Block - solid model - Generic 1 - support detail
    Browning Hi-Power Magazine Block – solid model – Generic 1 – support detail

    The main shape now has a 2 mm taper to ease the magazine spring past the upper edge of the block. The horn remains slightly inset from the side walls to ensure that the whole thing remains manifold:

    Browning Hi-Power Magazine Block - solid model - Generic 1 - whole end
    Browning Hi-Power Magazine Block – solid model – Generic 1 – whole end

    The whole object looks about the same, though:

    Browning Hi-Power Magazine Block - solid model - Generic 1 - whole side
    Browning Hi-Power Magazine Block – solid model – Generic 1 – whole side

    The shape descends from the geometry I used for the stainless steel block, with the additional internal channel (on the right in the models) to be filled with steel-loaded epoxy during assembly. That should make the whole block sufficiently robust that you must destroy the floorplate and distort the spring to get it out; wrecking the magazine’s innards should count as not “readily” modifiable.

    Some destructive testing seems to be in order…

    The OpenSCAD source code:

    // Browning Hi-Power Magazine Plug
    // Ed Nisley KE4ZNU December 2013
    //	February 2014 - easier customization for different magazine measurements
    
    Layout = "Whole";			// Whole Show Split
    							//  Whole = upright for steel or plastic
    							//  Show = section view for demo, not for building
    							//  Split = laid flat for plastic show-n-tell assembly
    
    AlignPins = true && (Layout == "Split");	// pins only for split show-n-tell
    
    Support = true && (Layout != "Split");		// no support for split, optional otherwise
    
    // Define magazine measurements
    
    //BlockData =  [-0.5, 1.5, 11.5];		// Browning OEM
    BlockData = [-1.5, 2.0, 9.0];		// Generic 1
    
    SCREWOFFSET = 0;
    CRIMPHEIGHT = 1;
    CRIMPDISTANCE = 2;
    
    //- Extrusion parameters must match reality!
    //  Print with 2 shells and 3 solid layers
    
    ThreadThick = 0.20;
    ThreadWidth = 0.40;
    
    HoleWindage = 0.2;
    
    Protrusion = 0.1;			// make holes end cleanly
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    //----------------------
    // Dimensions
    
    Angle = 12.5;				// from vertical
    
    SpringID = 10.3;			// magazine spring curvature (measure with drill shank)
    SpringRadius = SpringID / 2;
    Taper = 2.0;				// total taper toward top
    
    Length = 24.5;				// front-to-back perpendicular to magazine shaft
    Height = 17.0;				// bottom-to-top, parallel to magazine shaft
    
    RectLength = Length - SpringID;	// block length between end radii
    
    HornBaseOD = 8.0;			// fits between follower pegs to prevent shortening
    HornTipOD = 5.0;
    HornAddTip = (HornTipOD/2)*tan(Angle);
    HornAddBase = (HornBaseOD/2)*tan(Angle);
    HornAddLength = HornAddTip + HornAddBase + 2*Protrusion;
    HornLength = 12.0;			// should recompute ODs, but *eh*
    
    ScrewOD = 3.0 - 0.25;		// screw hole dia - minimal thread engagement
    ScrewLength = Height - 5.0;
    ScrewOffset = BlockData[SCREWOFFSET];	//   ... from centerline on XY plane
    
    NutOD = 5.8;						// hex nut dia across flats
    NutThick = 2.4;						//  ... generous allowance for nut
    NutTrapLength = 1.5*NutThick;		// allow for epoxy buildup
    NutTrapBaseHeight = 5.0;			//  ... base height from floor plate
    
    CrimpHeight = IntegerMultiple(BlockData[CRIMPHEIGHT],ThreadThick);		// vertical clearance for spring crimp tab on base plate
    
    CrimpDistance = BlockData[CRIMPDISTANCE];		//  ... clip to screw hole center
    CrimpOffset = -(CrimpDistance - ScrewOffset);	// ... horizontal from centerline
    
    SupportLength = 4.0;		// length of support struts under Trim
    SupportWidth = IntegerMultiple(0.9*SpringID,4*ThreadWidth);	// ... size needed for platform adhesion
    SupportThick = CrimpHeight - ThreadThick;	// ... clearance for EZ removal
    
    VentDia = 2.5;				// air vent from back of screw recess
    //VentOffset = CrimpOffset + VentDia/2 + 5*ThreadWidth;
    VentOffset = -(NutOD + 4*ThreadWidth);
    VentLength = ScrewLength + VentDia;
    
    RecessDia = 3.5;			// additional air vent + weight reduction
    RecessLength = ScrewLength + RecessDia/2;		//  ... internal length
    RecessOffset = Length/2 - RecessDia/2 - 5*ThreadWidth;	//  ... offset from centerline
    
    PinOD = 1.72;				// alignment pins
    PinLength = 4.0;
    PinInset = 0.6*SpringRadius;	// from outside edges
    echo(str("Alignment pin length: ",PinLength));
    
    NumSides = 8*4;				// default cylinder sides
    
    Offset = 5.0/2;				// from centerline for build layout
    
    //----------------------
    // Useful routines
    
    function Delta(a,l) = l*tan(a);				// incremental length due to angle
    
    // Locating pin hole with glue recess
    //  Default length is two pin diameters on each side of the split
    
    module LocatingPin(Dia=PinOD,Len=0.0) {
    
    	PinLen = (Len != 0.0) ? Len : (4*Dia);
    
    	translate([0,0,-ThreadThick])
    		PolyCyl((Dia + 2*ThreadWidth),2*ThreadThick,4);
    
    	translate([0,0,-2*ThreadThick])
    		PolyCyl((Dia + 1*ThreadWidth),4*ThreadThick,4);
    
    	translate([0,0,-(Len/2 + ThreadThick)])
    		PolyCyl(Dia,(Len + 2*ThreadThick),4);
    
    }
    
    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) {
    
      Range = floor(50 / Space);
    
    	for (x=[-Range:Range])
    	  for (y=[-Range:Range])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //----------------------
    // The magazine block
    
    module Block(SectionSelect = 0) {
    
    CropHeight = Height*cos(Angle);				// block height perpendicular to base
    echo(str("Perpendicular height: ",CropHeight));
    
    	difference() {
    		union() {
    			intersection() {
    				rotate([Angle,0,0])
    					hull() {
    						for (i=[-1,1])
    							translate([0,i*RectLength/2,-((Length/2)*sin(Angle) + Protrusion)])
    								cylinder(r1=SpringRadius,r2=(SpringRadius - Taper/2),
    										 h=(Height + 2*(Length/2)*sin(Angle) + 2*Protrusion),
    										 $fn=NumSides);
    					}
    				translate([0,0,CropHeight/2])
    					cube([2*SpringID,3*Length,CropHeight],center=true);
    			}
    			translate([0,-Height*sin(Angle),Height*cos(Angle)])
    				resize([(SpringID - Taper),0,0])
    					intersection() {
    						rotate([Angle,0,0])
    							translate([0,0,-(HornAddBase + Protrusion)])
    								cylinder(r1=HornBaseOD/2,
    										r2=HornTipOD/2,
    										h=(HornLength + HornAddLength + Protrusion),
    										$fn=NumSides);
    					cube([2*SpringID,Length,2*(HornLength*cos(Angle) + Protrusion)],center=true);
    				}
    		}
    
    		translate([0,ScrewOffset,-Protrusion])		// screw
    			rotate(180/6)
    				PolyCyl(ScrewOD,(ScrewLength + Protrusion),6);
    
    		translate([0,ScrewOffset,NutTrapBaseHeight])		// nut trap in center
    			rotate(180/6)
    				PolyCyl(NutOD,NutTrapLength,6);
    
    		translate([0,ScrewOffset,-Protrusion])		// nut clearance at base
    			rotate(180/6)
    				PolyCyl(NutOD,(1.1*NutThick + Protrusion),6);
    
    		translate([SpringID/2,CrimpOffset,-Protrusion])
    			rotate(180)
    				cube([SpringID,Length,(CrimpHeight + Protrusion)],center=false);
    
    		if (AlignPins)								// alignment pins
    			if (true)
    				translate([0,-CropHeight*tan(Angle),CropHeight])
    					rotate([0,90,0]) rotate(45 + Angle)
    						LocatingPin(PinOD,PinLength);
    			else
    			for (i=[-1,1])			// cannot use these with additional vents * channels
    				rotate([Angle,0,0])
    				translate([0,
    							(i*((Length/2)*cos(Angle) - PinInset)),
    							(CropHeight/2 - i*2*PinInset)])
    					rotate([0,90,0]) rotate(45 - Angle)
    						LocatingPin(PinOD,PinLength);
    
    		translate([0,(ScrewOffset + 1.25*NutOD),ScrewLength])	// air vent
    			rotate([90,0,0]) rotate(180/8)
    				PolyCyl(VentDia,3*NutOD,8);
    		translate([0,VentOffset,-(VentDia/2)*tan(Angle)])
    			rotate([Angle,0,0]) rotate(180/8)
    				PolyCyl(VentDia,VentLength,8);
    
    		translate([0,RecessOffset,0])			// weight reduction recess
    			rotate([Angle,0,0]) rotate(180/8)
    				translate([0,0,-((RecessDia/2)*tan(Angle))])
    				PolyCyl(RecessDia,(RecessLength + (RecessDia/2)*tan(Angle)),8);
    
    		if (SectionSelect == 1)
    			translate([0*SpringID,-2*Length,-Protrusion])
    				cube([2*SpringID,4*Length,(Height + HornLength + 2*Protrusion)],center=false);
    		else if (SectionSelect == -1)
    			translate([-2*SpringID,-2*Length,-Protrusion])
    				cube([2*SpringID,4*Length,(Height + HornLength + 2*Protrusion)],center=false);
    	}
    
    SupportSlots = (SupportWidth / (4*ThreadWidth)) / 2;		// SupportWidth is multiple of 4*ThreadWidth
    
    	if (Support)
    	color("Yellow") {
    		translate([0,(CrimpOffset - SupportLength/2),SupportThick/2])
    			difference() {
    				translate([0,-ThreadWidth,0])
    					cube([(SupportWidth - Protrusion),SupportLength,SupportThick],center=true);
    				for (i=[-SupportSlots:SupportSlots])
    					translate([i*4*ThreadWidth + 0*ThreadWidth,ThreadWidth,0])
    						cube([(2*ThreadWidth),SupportLength,(SupportThick + 2*Protrusion)],center=true);
    			}
    
    		translate([0,ScrewOffset,0])
    			for (j=[0:5]) {
    				rotate(30 + 360*j/6)
    					translate([(NutOD/2 - ThreadWidth)/2,0,(1.1*NutThick - ThreadThick)/2])
    						color("Yellow")
    						cube([(NutOD/2 - ThreadWidth),
    							(2*ThreadWidth),
    							(1.1*NutThick - ThreadThick)],
    							center=true);
    			}
    
    	}
    
    }
    
    //-------------------
    // Build it...
    
    ShowPegGrid();
    
    if (Layout == "Show")
    	Block(1);
    
    if (Layout == "Whole")
    	Block(0);
    
    if (Layout ==  "Split") {
    	translate([(Offset + Length/2),Height/2,0])
    		rotate(90) rotate([0,-90,-Angle])
    			Block(-1);
    	translate([-(Offset + Length/2),Height/2,0])
    		rotate(-90) rotate([0,90,Angle])
    			Block(1);
    }