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:

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

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:

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:

The whole object looks about the same, though:

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); }
Fyi: http://www.thefederalistpapers.org/video-2/ny-state-police-refuse-to-enforce-safe-act-magazine-limits
I still don’t understand why a seven round limit is “unsupported”, but a ten round limit is perfectly fine. But these days there’s a whole lotta stuff I don’t understand.
You and me both, in this line of business…