オシロスコープのプローブホルダーをOpenSCADで作ってみた


オシロスコープのプローブを引っ掛けておけるホルダーがあると便利ですが、意外と市販されていないので、作ってみました。
<img width=200 alt="プローブホルダー使用例" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1923193/dcfd1afe-6a44-b8f3-f56a-ca731ae3026d.jpeg">

OpenSCADで記述したので、パラメーターを変えるだけで、プローブを引っ掛ける穴の大きさ・数・位置や、壁の肉厚などを変更できて便利です。
Tektronixのプローブは細めなので穴径φ12、Keyshightや横河はφ13くらいにすると丁度いい感じです。
![プローブホルダー_OpenSCAD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1923193/25eb409c-c8e5-5817-d669-668c2cb15779.png)

JLCPCBの3Dプリンティングサービスで、2種類の材料(LEDO 6060 Resin(白), 3201PA-F Nylon(黒))、いくつかの形状で出力してみました。
最も安いLEDO 6060 Resin(白)で4穴タイプのものが$1でできるように肉厚を調整しています。
価格は体積に比例するようですが、1個あたりの最低価格が$1になっているようで、これ以上体積を減らしても安くはなりませんでした。
ちなみに4穴(φ13)タイプの3201PA-F Nylon(黒)は$2.37でした。
実物を手にとった感想は、いずれも強度は十分だと思いますが3201PA-F Nylon(黒)のほうがしなりがあって柔らかめです。
![プローブホルダーいろいろ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1923193/f74b72a7-ff23-e8b9-002b-a5f5563a6349.jpeg)

コードを以下に示します。
本番出力では$fsを0.2にしていますが、表示に時間がかかるので、試行錯誤する段階では0.5以上にしておくといいでしょう。
```verilog
// Probe Holder
$fa = 0.2;
$fs = 0.2;
//$fs = 0.5;

wt = 1.35; // wall thickness
iRd = 1;  // inner R
oRd = 1;  // outer R
oRd2 = 0.5;
dftS = 0.99; // draft scale
sizeX = 90;
sizeY = 25;
sizeZ = 25;
hPosX = [12, 34, 56, 78]; // probe holes positions(x)
hPosY = 16; // probe holes position(y)
hDia = 13;  // probe holes diameter
hSlit = 5;  // probe holes slit width
hst = 5;  // hole slit thickness
hDepth = 7; // probe holes depth

RibShaveCmp = (wt/2+iRd) * (wt/2+iRd)/(hDia/2+wt);

difference(){
  union(){
    difference(){
      linear_extrude(height=sizeZ){
        translate([oRd, oRd, 0]){
          offset(oRd){
            square([sizeX-(iRd*2), sizeY-(iRd*2)]);
          }
        }
      }
      translate([wt+iRd, wt+iRd, wt+iRd]){
        RoundEdgeCube(iRd, sizeX -(wt+iRd)*2, sizeY -(wt+iRd)*2, sizeZ, 1/dftS, 1);
      }
    }
    for (hx = hPosX){
      translate([hx, hPosY, 0]){
        difference(){
          r = hDia/2 + wt + iRd;
          cylinder(h=sizeZ, r1=r, r2=r*dftS);
          translate([0, 0, wt+iRd]){
            RoundEdgeTube(sizeZ, iRd, (hDia/2)+wt+iRd, scale=dftS);
          }
        }
        linear_extrude(height=sizeZ, scale=dftS){
          translate([-hSlit/2 -(wt+iRd), 0, 0]){
            square([hSlit+(wt+iRd)*2, sizeY-hPosY]);
          }
        }
      }
      translate([hx - wt/2 - iRd, 0, 0]){ //rib(Y)
        cube([wt+(iRd*2), hPosY, sizeZ]);
      }
    }
    translate([0, hPosY-wt/2-iRd, 0]){  //rib(X)
      cube([sizeX, wt+iRd*2, sizeZ]);
    }
  }
  for (hx = hPosX){
    translate([hx, hPosY, 0]){
      r = hDia/2;
      cylinder(h=sizeZ, r1=r, r2=r*dftS);
      linear_extrude(height=sizeZ, scale=1/dftS){
        translate([-hSlit/2,0,0]){
          square([hSlit, sizeY]);
        }
      }
    }
    for (dx = [wt/2 + iRd, -(wt/2 + iRd)]){
      translate([hx + dx, wt+iRd, wt+iRd]){ //rib(Y)
        y = hPosY - hDia/2 - (wt+iRd)*2 +
         RibShaveCmp *0.5;
        RoundEdgeCube(iRd, 0, y, sizeZ, 1/dftS, 1);
      }
    }
  }
  for (i =[0: len(hPosX)-2]){ //rib(X)
    for (dy = [wt/2+iRd, -(wt/2+iRd)]){
      translate([hPosX[i] + hDia/2 + wt + iRd - RibShaveCmp/2, hPosY + dy, wt+iRd]){
        x = hPosX[i+1] - hPosX[i] - hDia - (wt+iRd)*2 + RibShaveCmp;
        RoundEdgeCube(iRd, x, 0, sizeZ, 1/dftS, 1);
      }
    }
  }
  for (p = [[0, hPosX[0] - hDia/2, 0],
   [hPosX[len(hPosX)-1] + hDia/2, sizeX, -RibShaveCmp * 0.5]]){
    for (dy = [wt/2+iRd, -(wt/2+iRd)]){  //rib(X)
      translate([p[0] + p[2] + wt + iRd, hPosY + dy, wt+iRd]){
        RoundEdgeCube(iRd, p[1] - p[0] - (wt+iRd)*2 + RibShaveCmp * 0.5, 0, sizeZ, 1/dftS, 1);
      }
    }
  }
  translate([wt+iRd, wt+iRd, hDepth+iRd]){
    RoundEdgeCube(iRd, sizeX - (wt+iRd)*2, sizeY, sizeZ, 1/dftS, 1);
  }
  translate([0, sizeY, sizeZ]){
    rotate([0, 90, 0]){
      scale([(sizeZ-hst)/(sizeZ-wt), 1, 1]){
        cylinder(h=sizeX, r=sizeY-wt);
      }
    }
  }
  for (x = hPosX){
    echo(x, sizeY);
    for (d = [[hSlit/2, -90], [-hSlit/2, 180]]){
      translate([x + d[0], sizeY, 0]){
        rotate([0, 0, d[1]]){
          EdgeRounder(iRd, sizeZ);
        }
      }
    }
  }
  translate([0, sizeY, hst]){
    rotate([-90, -3, -90]){
      #EdgeRounder(oRd2, sizeX, 95);
    }
  }
}

module RoundEdgeCube(r, x, y, z, xscale=1, yscale=1){
  xd = x*(1-xscale)/2;
  yd = y*(1-yscale)/2;
  points = [[0, 0, 0], [x, 0, 0], [x, y, 0], [0, y, 0],
    [0+xd, 0+yd, z], [x-xd, 0+yd, z], [x-xd, y-yd, z], [0+xd, y-yd, z]];
  faces = [ [0,1,2,3], [4,5,1,0], [7,6,5,4],
  [5,6,2,1], [6,7,3,2], [7,4,0,3]];
  minkowski(){
    polyhedron(points, faces);
    sphere(r);
  }
}

module RoundEdgeTube(h, r1, r2, scale=1){
  rotate_extrude(){
    hull(){
      for(p = [[r2, 0, 0], [r2*scale, h, 0]]){
        translate(p){
          circle(r1);
        }
      }
    }
  }
}

module EdgeRounder(r, h, angle=90, thickness=0.1){
  difference(){
    ox = r / tan(angle/2);
    y = ox * sin(angle);
    lx = ox * cos(angle);
    linear_extrude(h){
      polygon([[-thickness, -thickness], [ox, -thickness], [ox -thickness, y], [lx, y]]);
    }
    translate([ox, r, 0]){
      cylinder(h, r, r);
    }
  }
}
```