1/**
  2 * @file gridfinity-rebuilt-utility.scad
  3 * @brief UTILITY FILE, DO NOT EDIT
  4 *        EDIT OTHER FILES IN REPO FOR RESULTS
  5 */
  6
  7include <standard.scad>
  8use <generic-helpers.scad>
  9
 10// ===== User Modules ===== //
 11
 12// functions to convert gridz values to mm values
 13
 14/**
 15 * @Summary Convert a number from Gridfinity values to mm.
 16 * @details Also can include lip when working with height values.
 17 * @param gridfinityUnit Gridfinity is normally on a base 7 system.
 18 * @param includeLipHeight Include the lip height as well.
 19 * @returns The final value in mm.
 20 */
 21function fromGridfinityUnits(gridfinityUnit, includeLipHeight = false) =
 22    gridfinityUnit*7 + (includeLipHeight ? h_lip : 0);
 23
 24/**
 25 * @Summary Height in mm including fixed heights.
 26 * @details Also can include lip when working with height values.
 27 * @param mmHeight Height without other values.
 28 * @param includeLipHeight Include the lip height as well.
 29 * @returns The final value in mm.
 30 */
 31function includingFixedHeights(mmHeight, includeLipHeight = false) =
 32    mmHeight + h_bot + h_base + (includeLipHeight ? h_lip : 0);
 33
 34/**
 35 * @brief Three Functions in One. For height calculations.
 36 * @param z Height value
 37 * @param gridz_define As explained in gridfinity-rebuilt-bins.scad
 38 * @param l style_lip as explained in gridfinity-rebuilt-bins.scad
 39 * @returns Height in mm
 40 */
 41function hf (z, gridz_define, style_lip) =
 42        gridz_define==0 ? fromGridfinityUnits(z, style_lip==2) :
 43        gridz_define==1 ? includingFixedHeights(z, style_lip==2) :
 44        z + ( // Just use z (possibly adding/subtracting lip)
 45            style_lip==1 ? -h_lip :
 46            style_lip==2 ? h_lip : 0
 47        )
 48    ;
 49
 50/**
 51 * @brief Calculates the proper height for bins. Three Functions in One.
 52 * @param z Height value
 53 * @param d gridz_define as explained in gridfinity-rebuilt-bins.scad
 54 * @param l style_lip as explained in gridfinity-rebuilt-bins.scad
 55 * @param enable_zsnap Automatically snap the bin size to the nearest 7mm increment.
 56 * @returns Height in mm
 57 */
 58function height (z,d=0,l=0,enable_zsnap=true) =
 59    (
 60    enable_zsnap ? (
 61        (abs(hf(z,d,l))%7==0) ? hf(z,d,l) :
 62        hf(z,d,l)+7-abs(hf(z,d,l))%7
 63    )
 64    :hf(z,d,l)
 65    ) -h_base;
 66
 67// Creates equally divided cutters for the bin
 68//
 69// n_divx:  number of x compartments (ideally, coprime w/ gridx)
 70// n_divy:  number of y compartments (ideally, coprime w/ gridy)
 71//          set n_div values to 0 for a solid bin
 72// style_tab:   tab style for all compartments. see cut()
 73// scoop_weight:    scoop toggle for all compartments. see cut()
 74module cutEqual(n_divx=1, n_divy=1, style_tab=1, scoop_weight=1) {
 75    for (i = [1:n_divx])
 76    for (j = [1:n_divy])
 77    cut((i-1)*$gxx/n_divx,(j-1)*$gyy/n_divy, $gxx/n_divx, $gyy/n_divy, style_tab, scoop_weight);
 78}
 79
 80
 81// Creates equally divided cylindrical cutouts
 82//
 83// n_divx: number of x cutouts
 84// n_divy: number of y cutouts
 85//         set n_div values to 0 for a solid bin
 86// cylinder_diameter: diameter of cutouts
 87// cylinder_height: height of cutouts
 88// coutout_depth: offset from top to solid part of container
 89// orientation: orientation of cylinder cutouts (0 = x direction, 1 = y direction, 2 = z direction)
 90// chamfer: chamfer around the top rim of the holes
 91module cutCylinders(n_divx=1, n_divy=1, cylinder_diameter=1, cylinder_height=1, coutout_depth=0, orientation=0, chamfer=0.5) {
 92    rotation = (orientation == 0)
 93            ? [0,90,0]
 94            : (orientation == 1)
 95                ? [90,0,0]
 96                : [0,0,0];
 97
 98    gridx_mm = $gxx*l_grid;
 99    gridy_mm = $gyy*l_grid;
100    padding = 2;
101    cutout_x = gridx_mm - d_wall*2;
102    cutout_y = gridy_mm - d_wall*2;
103
104    cut_move(x=0, y=0, w=$gxx, h=$gyy) {
105        translate([0,0,-coutout_depth]) {
106            rounded_rectangle(cutout_x, cutout_y, coutout_depth*2, r_base);
107
108            pattern_linear(x=n_divx, y=n_divy, sx=(gridx_mm - padding)/n_divx, sy=(gridy_mm - padding)/n_divy)
109                rotate(rotation)
110                    union() {
111                        cylinder(d=cylinder_diameter, h=cylinder_height*2, center=true);
112                        if (chamfer > 0) {
113                            translate([0,0,-chamfer]) cylinder(d1=cylinder_diameter, d2=cylinder_diameter+4*chamfer, h=2*chamfer);
114                        }
115                    };
116        }
117    }
118}
119
120// initialize gridfinity
121// sl:  lip style of this bin.
122//      0:Regular lip, 1:Remove lip subtractively, 2:Remove lip and retain height
123module gridfinityInit(gx, gy, h, h0 = 0, l = l_grid, sl = 0) {
124    $gxx = gx;
125    $gyy = gy;
126    $dh = h;
127    $dh0 = h0;
128    $style_lip = sl;
129    difference() {
130        color("firebrick")
131        block_bottom(h0==0?$dh-0.1:h0, gx, gy, l);
132        children();
133    }
134    color("royalblue")
135    block_wall(gx, gy, l) {
136        if ($style_lip == 0) profile_wall(h);
137        else profile_wall2(h);
138    }
139}
140// Function to include in the custom() module to individually slice bins
141// Will try to clamp values to fit inside the provided base size
142//
143// x:   start coord. x=1 is the left side of the bin.
144// y:   start coord. y=1 is the bottom side of the bin.
145// w:   width of compartment, in # of bases covered
146// h:   height of compartment, in # of basese covered
147// t:   tab style of this specific compartment.
148//      alignment only matters if the compartment size is larger than d_tabw
149//      0:full, 1:auto, 2:left, 3:center, 4:right, 5:none
150//      Automatic alignment will use left tabs for bins on the left edge, right tabs for bins on the right edge, and center tabs everywhere else.
151// s:   toggle the rounded back corner that allows for easy removal
152
153module cut(x=0, y=0, w=1, h=1, t=1, s=1, tab_width=d_tabw, tab_height=d_tabh) {
154    translate([0,0,-$dh-h_base])
155    cut_move(x,y,w,h)
156    block_cutter(clp(x,0,$gxx), clp(y,0,$gyy), clp(w,0,$gxx-x), clp(h,0,$gyy-y), t, s, tab_width, tab_height);
157}
158
159
160// cuts equally sized bins over a given length at a specified position
161// bins_x:  number of bins along x-axis
162// bins_y:  number of bins along y-axis
163// len_x:   length (in gridfinity bases) along x-axis that the bins_x will fill
164// len_y:   length (in gridfinity bases) along y-axis that the bins_y will fill
165// pos_x:   start x position of the bins (left side)
166// pos_y:   start y position of the bins (bottom side)
167// style_tab:   Style of the tab used on the bins
168// scoop:   Weight of the scoop on the bottom of the bins
169// tab_width:   Width of the tab on the bins, in mm.
170// tab_height:  How far the tab will stick out over the bin, in mm. Default tabs fit 12mm labels, but for narrow bins can take up too much space over the opening. This setting allows 'slimmer' tabs for use with thinner labels, so smaller/narrower bins can be labeled and still keep a reasonable opening at the top. NOTE: The measurement is not 1:1 in mm, so a '3.5' value does not guarantee a tab that fits 3.5mm label tape. Use the 'measure' tool after rendering to check the distance between faces to guarantee it fits your needs.
171module cutEqualBins(bins_x=1, bins_y=1, len_x=1, len_y=1, pos_x=0, pos_y=0, style_tab=5, scoop=1, tab_width=d_tabw, tab_height=d_tabh) {
172    // Calculate width and height of each bin based on total length and number of bins
173    bin_width = len_x / bins_x;
174    bin_height = len_y / bins_y;
175
176    // Loop through each bin position in x and y direction
177    for (i = [0:bins_x-1]) {
178        for (j = [0:bins_y-1]) {
179            // Calculate the starting position for each bin
180            // Adjust position by adding pos_x and pos_y to shift the entire grid of bins as needed
181            bin_start_x = pos_x + i * bin_width;
182            bin_start_y = pos_y + j * bin_height;
183
184            // Call the cut module to create each bin with calculated position and dimensions
185            // Pass through the style_tab and scoop parameters
186            cut(bin_start_x, bin_start_y, bin_width, bin_height, style_tab, scoop, tab_width=tab_width, tab_height=tab_height);
187        }
188    }
189}
190
191// Translates an object from the origin point to the center of the requested compartment block, can be used to add custom cuts in the bin
192// See cut() module for parameter descriptions
193module cut_move(x, y, w, h) {
194    translate([0,0,$dh0==0?$dh+h_base:$dh0+h_base])
195    cut_move_unsafe(clp(x,0,$gxx), clp(y,0,$gyy), clp(w,0,$gxx-x), clp(h,0,$gyy-y))
196    children();
197}
198
199// ===== Modules ===== //
200
201module profile_base() {
202    polygon([
203        [0,0],
204        [0,h_base],
205        [r_base,h_base],
206        [r_base-r_c2,h_base-r_c2],
207        [r_base-r_c2,r_c1],
208        [r_base-r_c2-r_c1,0]
209    ]);
210}
211
212module gridfinityBase(gx, gy, l, dx, dy, style_hole, off=0, final_cut=true, only_corners=false) {
213    dbnxt = [for (i=[1:5]) if (abs(gx*i)%1 < 0.001 || abs(gx*i)%1 > 0.999) i];
214    dbnyt = [for (i=[1:5]) if (abs(gy*i)%1 < 0.001 || abs(gy*i)%1 > 0.999) i];
215    dbnx = 1/(dx==0 ? len(dbnxt) > 0 ? dbnxt[0] : 1 : round(dx));
216    dbny = 1/(dy==0 ? len(dbnyt) > 0 ? dbnyt[0] : 1 : round(dy));
217    xx = gx*l-0.5;
218    yy = gy*l-0.5;
219
220    if (final_cut)
221    translate([0,0,h_base])
222    rounded_rectangle(xx+0.002, yy+0.002, h_bot/1.5, r_fo1+0.001);
223
224    intersection(){
225        if (final_cut)
226        translate([0,0,-1])
227        rounded_rectangle(xx+0.005, yy+0.005, h_base+h_bot/2*10, r_fo1+0.001);
228
229        if((style_hole != 0) && (only_corners)) {
230            difference(){
231                pattern_linear(gx/dbnx, gy/dbny, dbnx*l, dbny*l)
232                block_base(gx, gy, l, dbnx, dbny, 0, off);
233                if (style_hole == 4) {
234                    translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
235                    refined_hole();
236                    mirror([1, 0, 0])
237                    translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
238                    refined_hole();
239                    mirror([0, 1, 0]) {
240                        translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
241                        refined_hole();
242                        mirror([1, 0, 0])
243                        translate([(gx/2)*l_grid - d_hole_from_side, (gy/2) * l_grid - d_hole_from_side, h_slit*2])
244                        refined_hole();
245                    }
246                }
247                else {
248                    pattern_linear(2, 2, (gx-1)*l_grid+d_hole, (gy-1)*l_grid+d_hole)
249                    block_base_hole(style_hole, off);
250                }
251            }
252        }
253        else {
254            pattern_linear(gx/dbnx, gy/dbny, dbnx*l, dbny*l)
255            block_base(gx, gy, l, dbnx, dbny, style_hole, off);
256        }
257    }
258}
259
260/**
261 * @brief A single Gridfinity base.
262 * @param gx
263 * @param gy
264 * @param l
265 * @param dbnx
266 * @param dbny
267 * @param style_hole
268 * @param off
269 */
270module block_base(gx, gy, l, dbnx, dbny, style_hole, off) {
271    render(convexity = 2)
272    difference() {
273        block_base_solid(dbnx, dbny, l, off);
274
275        if (style_hole > 0)
276            pattern_circular(abs(l-d_hole_from_side/2)<0.001?1:4)
277            if (style_hole == 4)
278                translate([l/2-d_hole_from_side, l/2-d_hole_from_side, h_slit*2])
279                refined_hole();
280            else
281                translate([l/2-d_hole_from_side, l/2-d_hole_from_side, 0])
282                block_base_hole(style_hole, off);
283        }
284}
285
286/**
287 * @brief A gridfinity base with no holes.
288 * @details Used as the "base" with holes removed from it later.
289 * @param dbnx
290 * @param dbny
291 * @param l
292 * @param o
293 */
294module block_base_solid(dbnx, dbny, l, o) {
295    xx = dbnx*l-0.05;
296    yy = dbny*l-0.05;
297    oo = (o/2)*(sqrt(2)-1);
298    translate([0,0,h_base])
299    mirror([0,0,1])
300    union() {
301        hull() {
302            rounded_rectangle(xx-2*r_c2-2*r_c1+o, yy-2*r_c2-2*r_c1+o, h_base+oo, r_fo3);
303            rounded_rectangle(xx-2*r_c2+o, yy-2*r_c2+o, h_base-r_c1+oo, r_fo2);
304        }
305        translate([0,0,oo])
306        hull() {
307            rounded_rectangle(xx-2*r_c2+o, yy-2*r_c2+o, r_c2, r_fo2);
308            mirror([0,0,1])
309            rounded_rectangle(xx+o, yy+o, h_bot/2+abs(10*o), r_fo1);
310        }
311    }
312}
313
314module block_base_hole(style_hole, o=0) {
315    r1 = r_hole1-o/2;
316    r2 = r_hole2-o/2;
317    union() {
318        difference() {
319            cylinder(h = 2*(h_hole-o+(style_hole==3?h_slit:0)), r=r2, center=true);
320
321            if (style_hole==3)
322            copy_mirror([0,1,0])
323            translate([-1.5*r2,r1+0.1,h_hole-o])
324            cube([r2*3,r2*3, 10]);
325        }
326        if (style_hole > 1)
327        cylinder(h = 2*h_base-o, r = r1, center=true);
328    }
329}
330
331
332module refined_hole() {
333    /**
334    * Refined hole based on Printables @grizzie17's Gridfinity Refined
335    * https://www.printables.com/model/413761-gridfinity-refined
336    */
337
338    // Meassured magnet hole diameter to be 5.86mm (meassured in fusion360
339    r = r_hole2-0.32;
340
341    // Magnet height
342    m = 2;
343    mh = m-0.1;
344
345    // Poke through - For removing a magnet using a toothpick
346    ptl = h_slit*3; // Poke Through Layers
347    pth = mh+ptl; // Poke Through Height
348    ptr = 2.5; // Poke Through Radius
349
350    union() {
351        hull() {
352            // Magnet hole - smaller than the magnet to keep it squeezed
353            translate([10, -r, 0]) cube([1, r*2, mh]);
354            cylinder(1.9, r=r);
355        }
356        hull() {
357            // Poke hole
358            translate([-9+5.60, -ptr/2, -ptl]) cube([1, ptr, pth]);
359            translate([-12.53+5.60, 0, -ptl]) cylinder(pth, d=ptr);
360        }
361    }
362}
363
364/**
365 * @brief Stacking lip based on https://gridfinity.xyz/specification/
366 * @details Also includes a support base.
367 */
368module stacking_lip() {
369    // Technique: Descriptive constant names are useful, but can be unweildy.
370    // Use abbreviations if they are going to be re-used repeatedly in a small piece of code.
371    inner_slope = stacking_lip_inner_slope_height_mm;
372    wall_height = stacking_lip_wall_height_mm;
373
374    support_wall = stacking_lip_support_wall_height_mm;
375    s_total = stacking_lip_support_height_mm;
376
377    polygon([
378        [0, 0], // Inner tip
379        [inner_slope, inner_slope], // Go out 45 degrees
380        [inner_slope, inner_slope+wall_height], // Vertical increase
381        [stacking_lip_depth, stacking_lip_height], // Go out 45 degrees
382        [stacking_lip_depth, -s_total], // Down to support bottom
383        [0, -support_wall], // Up and in
384        [0, 0] // Close the shape. Tehcnically not needed.
385    ]);
386}
387
388/**
389 * @brief Stacking lip with a with a chamfered (rounded) top.
390 * @details Based on https://gridfinity.xyz/specification/
391 *          Also includes a support base.
392 */
393module stacking_lip_chamfered() {
394    radius_center_y = h_lip - r_f1;
395
396    union() {
397        // Create rounded top
398        intersection() {
399            translate([0, radius_center_y, 0])
400            square([stacking_lip_depth, stacking_lip_height]);
401            offset(r = r_f1)
402            offset(delta = -r_f1)
403            stacking_lip();
404        }
405        // Remove pointed top
406        difference(){
407            stacking_lip();
408            translate([0, radius_center_y, 0])
409            square([stacking_lip_depth*2, stacking_lip_height*2]);
410        }
411    }
412}
413
414/**
415 * @brief External wall profile, with a stacking lip.
416 * @details Translated so a 90 degree rotation produces the expected outside radius.
417 */
418module profile_wall(height_mm) {
419    assert(is_num(height_mm))
420    translate([r_base - stacking_lip_depth, 0, 0]){
421        translate([0, height_mm, 0])
422        stacking_lip_chamfered();
423        translate([stacking_lip_depth-d_wall/2, 0, 0])
424        square([d_wall/2, height_mm]);
425    }
426}
427
428// lipless profile
429module profile_wall2(height_mm) {
430    assert(is_num(height_mm))
431    translate([r_base,0,0])
432    mirror([1,0,0])
433    square([d_wall, height_mm]);
434}
435
436module block_wall(gx, gy, l) {
437    translate([0,0,h_base])
438    sweep_rounded(gx*l-2*r_base-0.5-0.001, gy*l-2*r_base-0.5-0.001)
439    children();
440}
441
442module block_bottom( h = 2.2, gx, gy, l ) {
443    translate([0,0,h_base+0.1])
444    rounded_rectangle(gx*l-0.5-d_wall/4, gy*l-0.5-d_wall/4, h, r_base+0.01);
445}
446
447module cut_move_unsafe(x, y, w, h) {
448    xx = ($gxx*l_grid+d_magic);
449    yy = ($gyy*l_grid+d_magic);
450    translate([(x)*xx/$gxx,(y)*yy/$gyy,0])
451    translate([(-xx+d_div)/2,(-yy+d_div)/2,0])
452    translate([(w*xx/$gxx-d_div)/2,(h*yy/$gyy-d_div)/2,0])
453    children();
454}
455
456module block_cutter(x,y,w,h,t,s,tab_width=d_tabw,tab_height=d_tabh) {
457
458    v_len_tab = tab_height;
459    v_len_lip = d_wall2-d_wall+1.2;
460    v_cut_tab = tab_height - (2*r_f1)/tan(a_tab);
461    v_cut_lip = d_wall2-d_wall-d_clear;
462    v_ang_tab = a_tab;
463    v_ang_lip = 45;
464
465    ycutfirst = y == 0 && $style_lip == 0;
466    ycutlast = abs(y+h-$gyy)<0.001 && $style_lip == 0;
467    xcutfirst = x == 0 && $style_lip == 0;
468    xcutlast = abs(x+w-$gxx)<0.001 && $style_lip == 0;
469    zsmall = ($dh+h_base)/7 < 3;
470
471    ylen = h*($gyy*l_grid+d_magic)/$gyy-d_div;
472    xlen = w*($gxx*l_grid+d_magic)/$gxx-d_div;
473
474    height = $dh;
475    extent = (abs(s) > 0 && ycutfirst ? d_wall2-d_wall-d_clear : 0);
476    tab = (zsmall || t == 5) ? (ycutlast?v_len_lip:0) : v_len_tab;
477    ang = (zsmall || t == 5) ? (ycutlast?v_ang_lip:0) : v_ang_tab;
478    cut = (zsmall || t == 5) ? (ycutlast?v_cut_lip:0) : v_cut_tab;
479    style = (t > 1 && t < 5) ? t-3 : (x == 0 ? -1 : xcutlast ? 1 : 0);
480
481    translate([0,ylen/2,h_base+h_bot])
482    rotate([90,0,-90]) {
483
484    if (!zsmall && xlen - tab_width > 4*r_f2 && (t != 0 && t != 5)) {
485        fillet_cutter(3,"bisque")
486        difference() {
487            transform_tab(style, xlen, ((xcutfirst&&style==-1)||(xcutlast&&style==1))?v_cut_lip:0, tab_width)
488            translate([ycutlast?v_cut_lip:0,0])
489            profile_cutter(height-h_bot, ylen/2, s);
490
491            if (xcutfirst)
492            translate([0,0,(xlen/2-r_f2)-v_cut_lip])
493            cube([ylen,height,v_cut_lip*2]);
494
495            if (xcutlast)
496            translate([0,0,-(xlen/2-r_f2)-v_cut_lip])
497            cube([ylen,height,v_cut_lip*2]);
498        }
499        if (t != 0 && t != 5)
500        fillet_cutter(2,"indigo")
501        difference() {
502            transform_tab(style, xlen, ((xcutfirst&&style==-1)||(xcutlast&&style==1)?v_cut_lip:0), tab_width)
503            difference() {
504                intersection() {
505                    profile_cutter(height-h_bot, ylen-extent, s);
506                    profile_cutter_tab(height-h_bot, v_len_tab, v_ang_tab);
507                }
508                if (ycutlast) profile_cutter_tab(height-h_bot, v_len_lip, 45);
509            }
510
511            if (xcutfirst)
512            translate([ylen/2,0,xlen/2])
513            rotate([0,90,0])
514            transform_main(2*ylen)
515            profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
516
517            if (xcutlast)
518            translate([ylen/2,0,-xlen/2])
519            rotate([0,-90,0])
520            transform_main(2*ylen)
521            profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
522        }
523    }
524
525    fillet_cutter(1,"seagreen")
526    translate([0,0,xcutlast?v_cut_lip/2:0])
527    translate([0,0,xcutfirst?-v_cut_lip/2:0])
528    transform_main(xlen-(xcutfirst?v_cut_lip:0)-(xcutlast?v_cut_lip:0))
529    translate([cut,0])
530    profile_cutter(height-h_bot, ylen-extent-cut-(!s&&ycutfirst?v_cut_lip:0), s);
531
532    fillet_cutter(0,"hotpink")
533    difference() {
534        transform_main(xlen)
535        difference() {
536            profile_cutter(height-h_bot, ylen-extent, s);
537
538            if (!((zsmall || t == 5) && !ycutlast))
539            profile_cutter_tab(height-h_bot, tab, ang);
540
541            if (!(abs(s) > 0)&& y == 0)
542            translate([ylen-extent,0,0])
543            mirror([1,0,0])
544            profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
545        }
546
547        if (xcutfirst)
548        color("indigo")
549        translate([ylen/2+0.001,0,xlen/2+0.001])
550        rotate([0,90,0])
551        transform_main(2*ylen)
552        profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
553
554        if (xcutlast)
555        color("indigo")
556        translate([ylen/2+0.001,0,-xlen/2+0.001])
557        rotate([0,-90,0])
558        transform_main(2*ylen)
559        profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
560    }
561
562    }
563}
564
565module transform_main(xlen) {
566    translate([0,0,-(xlen-2*r_f2)/2])
567    linear_extrude(xlen-2*r_f2)
568    children();
569}
570
571module transform_tab(type, xlen, cut, tab_width=d_tabw) {
572    mirror([0,0,type==1?1:0])
573    copy_mirror([0,0,-(abs(type)-1)])
574    translate([0,0,-(xlen)/2])
575    translate([0,0,r_f2])
576    linear_extrude((xlen-tab_width-abs(cut))/(1-(abs(type)-1))-2*r_f2)
577    children();
578}
579
580module fillet_cutter(t = 0, c = "goldenrod") {
581    color(c)
582    minkowski() {
583        children();
584        sphere(r = r_f2-t/1000);
585    }
586}
587
588module profile_cutter(h, l, s) {
589    scoop = max(s*$dh/2-r_f2,0);
590    translate([r_f2,r_f2])
591    hull() {
592        if (l-scoop-2*r_f2 > 0)
593            square(0.1);
594        if (scoop < h) {
595            translate([l-2*r_f2,h-r_f2/2])
596            mirror([1,1])
597            square(0.1);
598
599            translate([0,h-r_f2/2])
600            mirror([0,1])
601            square(0.1);
602        }
603        difference() {
604            translate([l-scoop-2*r_f2, scoop])
605            if (scoop != 0) {
606                intersection() {
607                    circle(scoop);
608                    mirror([0,1]) square(2*scoop);
609                }
610            } else mirror([1,0]) square(0.1);
611            translate([l-scoop-2*r_f2,-1])
612            square([-(l-scoop-2*r_f2),2*h]);
613
614            translate([0,h])
615            square([2*l,scoop]);
616        }
617    }
618}
619
620module profile_cutter_tab(h, tab, ang) {
621    if (tab > 0)
622        color("blue")
623        offset(delta = r_f2)
624        polygon([[0,h],[tab,h],[0,h-tab*tan(ang)]]);
625
626}