forked from kennetek/gridfinity-rebuilt-openscad
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gridfinity-rebuilt-utility.scad
616 lines (538 loc) · 21.7 KB
/
gridfinity-rebuilt-utility.scad
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
/**
* @file gridfinity-rebuilt-utility.scad
* @brief UTILITY FILE, DO NOT EDIT
* EDIT OTHER FILES IN REPO FOR RESULTS
*/
include <standard.scad>
use <generic-helpers.scad>
use <gridfinity-rebuilt-holes.scad>
use <external/threads-scad/threads.scad>
// ===== User Modules ===== //
// functions to convert gridz values to mm values
/**
* @Summary Convert a number from Gridfinity values to mm.
* @details Also can include lip when working with height values.
* @param gridfinityUnit Gridfinity is normally on a base 7 system.
* @param includeLipHeight Include the lip height as well.
* @returns The final value in mm.
*/
function fromGridfinityUnits(gridfinityUnit, includeLipHeight = false) =
gridfinityUnit*7 + (includeLipHeight ? STACKING_LIP_SIZE.y : 0);
/**
* @Summary Height in mm including fixed heights.
* @details Also can include lip when working with height values.
* @param mmHeight Height without other values.
* @param includeLipHeight Include the lip height as well.
* @returns The final value in mm.
*/
function includingFixedHeights(mmHeight, includeLipHeight = false) =
mmHeight + h_bot + h_base + (includeLipHeight ? STACKING_LIP_SIZE.y : 0);
/**
* @brief Three Functions in One. For height calculations.
* @param z Height value
* @param gridz_define As explained in gridfinity-rebuilt-bins.scad
* @param l style_lip as explained in gridfinity-rebuilt-bins.scad
* @returns Height in mm
*/
function hf (z, gridz_define, style_lip) =
gridz_define==0 ? fromGridfinityUnits(z, style_lip==2) :
gridz_define==1 ? includingFixedHeights(z, style_lip==2) :
gridz_define==2 ? z + (style_lip==2 ? STACKING_LIP_SIZE.y : 0) :
assert(false, "gridz_define must be 0, 1, or 2.")
;
/**
* @brief Calculates the proper height for bins. Three Functions in One.
* @Details Critically, this does not include the baseplate height.
* @param z Height value
* @param d gridz_define as explained in gridfinity-rebuilt-bins.scad
* @param l style_lip as explained in gridfinity-rebuilt-bins.scad
* @param enable_zsnap Automatically snap the bin size to the nearest 7mm increment.
* @returns Height in mm
*/
function height (z,d=0,l=0,enable_zsnap=true) =
(
enable_zsnap ? (
(abs(hf(z,d,l))%7==0) ? hf(z,d,l) :
hf(z,d,l)+7-abs(hf(z,d,l))%7
)
:hf(z,d,l)
) -h_base;
// Creates equally divided cutters for the bin
//
// n_divx: number of x compartments (ideally, coprime w/ gridx)
// n_divy: number of y compartments (ideally, coprime w/ gridy)
// set n_div values to 0 for a solid bin
// style_tab: tab style for all compartments. see cut()
// scoop_weight: scoop toggle for all compartments. see cut()
module cutEqual(n_divx=1, n_divy=1, style_tab=1, scoop_weight=1) {
for (i = [1:n_divx])
for (j = [1:n_divy])
cut((i-1)*$gxx/n_divx,(j-1)*$gyy/n_divy, $gxx/n_divx, $gyy/n_divy, style_tab, scoop_weight);
}
// Creates equally divided cylindrical cutouts
//
// n_divx: number of x cutouts
// n_divy: number of y cutouts
// set n_div values to 0 for a solid bin
// cylinder_diameter: diameter of cutouts
// cylinder_height: height of cutouts
// coutout_depth: offset from top to solid part of container
// orientation: orientation of cylinder cutouts (0 = x direction, 1 = y direction, 2 = z direction)
// chamfer: chamfer around the top rim of the holes
module cutCylinders(n_divx=1, n_divy=1, cylinder_diameter=1, cylinder_height=1, coutout_depth=0, orientation=0, chamfer=0.5) {
rotation = (orientation == 0)
? [0,90,0]
: (orientation == 1)
? [90,0,0]
: [0,0,0];
gridx_mm = $gxx*l_grid;
gridy_mm = $gyy*l_grid;
padding = 2;
cutout_x = gridx_mm - d_wall*2;
cutout_y = gridy_mm - d_wall*2;
cut_move(x=0, y=0, w=$gxx, h=$gyy) {
translate([0,0,-coutout_depth]) {
rounded_rectangle(cutout_x, cutout_y, coutout_depth*2, r_base);
pattern_linear(x=n_divx, y=n_divy, sx=(gridx_mm - padding)/n_divx, sy=(gridy_mm - padding)/n_divy)
rotate(rotation)
union() {
cylinder(d=cylinder_diameter, h=cylinder_height*2, center=true);
if (chamfer > 0) {
translate([0,0,-chamfer]) cylinder(d1=cylinder_diameter, d2=cylinder_diameter+4*chamfer, h=2*chamfer);
}
};
}
}
}
// initialize gridfinity
// sl: lip style of this bin.
// 0:Regular lip, 1:Remove lip subtractively, 2:Remove lip and retain height
module gridfinityInit(gx, gy, h, h0 = 0, l = l_grid, sl = 0) {
$gxx = gx;
$gyy = gy;
$dh = h;
$dh0 = h0;
$style_lip = sl;
difference() {
color("firebrick")
block_bottom(h0==0?$dh-0.1:h0, gx, gy, l);
children();
}
color("royalblue")
block_wall(gx, gy, l) {
if ($style_lip == 0) profile_wall(h);
else profile_wall2(h);
}
}
// Function to include in the custom() module to individually slice bins
// Will try to clamp values to fit inside the provided base size
//
// x: start coord. x=1 is the left side of the bin.
// y: start coord. y=1 is the bottom side of the bin.
// w: width of compartment, in # of bases covered
// h: height of compartment, in # of basese covered
// t: tab style of this specific compartment.
// alignment only matters if the compartment size is larger than d_tabw
// 0:full, 1:auto, 2:left, 3:center, 4:right, 5:none
// 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.
// s: toggle the rounded back corner that allows for easy removal
module cut(x=0, y=0, w=1, h=1, t=1, s=1, tab_width=d_tabw, tab_height=d_tabh) {
translate([0,0,-$dh-h_base])
cut_move(x,y,w,h)
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);
}
// cuts equally sized bins over a given length at a specified position
// bins_x: number of bins along x-axis
// bins_y: number of bins along y-axis
// len_x: length (in gridfinity bases) along x-axis that the bins_x will fill
// len_y: length (in gridfinity bases) along y-axis that the bins_y will fill
// pos_x: start x position of the bins (left side)
// pos_y: start y position of the bins (bottom side)
// style_tab: Style of the tab used on the bins
// scoop: Weight of the scoop on the bottom of the bins
// tab_width: Width of the tab on the bins, in mm.
// 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.
module 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) {
// Calculate width and height of each bin based on total length and number of bins
bin_width = len_x / bins_x;
bin_height = len_y / bins_y;
// Loop through each bin position in x and y direction
for (i = [0:bins_x-1]) {
for (j = [0:bins_y-1]) {
// Calculate the starting position for each bin
// Adjust position by adding pos_x and pos_y to shift the entire grid of bins as needed
bin_start_x = pos_x + i * bin_width;
bin_start_y = pos_y + j * bin_height;
// Call the cut module to create each bin with calculated position and dimensions
// Pass through the style_tab and scoop parameters
cut(bin_start_x, bin_start_y, bin_width, bin_height, style_tab, scoop, tab_width=tab_width, tab_height=tab_height);
}
}
}
// 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
// See cut() module for parameter descriptions
module cut_move(x, y, w, h) {
translate([0,0,$dh0==0?$dh+h_base:$dh0+h_base])
cut_move_unsafe(clp(x,0,$gxx), clp(y,0,$gyy), clp(w,0,$gxx-x), clp(h,0,$gyy-y))
children();
}
// ===== Modules ===== //
/**
*@summary Create the base of a gridfinity bin, or use it for a custom object.
* @param length X,Y size of a single Gridfinity base.
* @param thumbscrew Enable "gridfinity-refined" thumbscrew hole in the center of each base unit. This is a ISO Metric Profile, 15.0mm size, M15x1.5 designation.
*/
module gridfinityBase(gx, gy, length, dx, dy, hole_options=bundle_hole_options(), off=0, final_cut=true, only_corners=false, thumbscrew=false) {
assert(
is_num(gx) &&
is_num(gy) &&
is_num(length) &&
is_num(dx) &&
is_num(dy) &&
is_bool(final_cut) &&
is_bool(only_corners) &&
is_bool(thumbscrew)
);
dbnxt = [for (i=[1:5]) if (abs(gx*i)%1 < 0.001 || abs(gx*i)%1 > 0.999) i];
dbnyt = [for (i=[1:5]) if (abs(gy*i)%1 < 0.001 || abs(gy*i)%1 > 0.999) i];
dbnx = 1/(dx != 0 ? round(dx) : (len(dbnxt) > 0 ? dbnxt[0] : 1));
dbny = 1/(dy != 0 ? round(dy) : (len(dbnyt) > 0 ? dbnyt[0] : 1));
// Final size in number of bases
grid_size = [gx/dbnx, gy/dbny];
// Per spec, there's a 0.5mm gap between each base,
// But that needs to be scaled based on everything else.
individual_base_size_mm = [dbnx, dbny] * BASE_SIZE;
base_center_distance_mm = [dbnx, dbny] * length;
gap_mm = base_center_distance_mm - individual_base_size_mm;
// Final size of the base top. In mm.
grid_size_mm = [
base_center_distance_mm.x * grid_size.x,
base_center_distance_mm.y * grid_size.y,
] - gap_mm;
if (final_cut) {
translate([0, 0, h_base-TOLLERANCE])
rounded_square([grid_size_mm.x, grid_size_mm.y, h_bot], BASE_OUTSIDE_RADIUS, center=true);
}
if(only_corners) {
difference(){
pattern_linear(grid_size.x, grid_size.y, base_center_distance_mm.x, base_center_distance_mm.y)
block_base(bundle_hole_options(), 0, individual_base_size_mm, thumbscrew=thumbscrew);
copy_mirror([0, 1, 0]) {
copy_mirror([1, 0, 0]) {
translate([
grid_size_mm.x/2 - HOLE_DISTANCE_FROM_BOTTOM_EDGE - BASE_PROFILE_MAX.x,
grid_size_mm.y/2 - HOLE_DISTANCE_FROM_BOTTOM_EDGE - BASE_PROFILE_MAX.x,
0
])
block_base_hole(hole_options, off);
}
}
}
}
else {
pattern_linear(grid_size.x, grid_size.y, base_center_distance_mm.x, base_center_distance_mm.y)
block_base(hole_options, off, individual_base_size_mm, thumbscrew=thumbscrew);
}
}
/**
* @brief A single Gridfinity base. With holes (if set).
* @param hole_options @see block_base_hole.hole_options
* @param off
* @param size [x, y] size of a single base. Only set if deviating from the standard!
* @param thumbscrew Enable "gridfinity-refined" thumbscrew hole in the center of each base unit. This is a ISO Metric Profile, 15.0mm size, M15x1.5 designation.
*/
module block_base(hole_options, off=0, size=[BASE_SIZE, BASE_SIZE], thumbscrew=false) {
assert(
is_list(size) &&
len(size) == 2 &&
is_bool(thumbscrew)
);
// How far, in the +x direction,
// the profile needs to be from it's [0, 0] point
// such that when swept by 90 degrees to produce a corner,
// the outside edge has the desired radius.
translation_x = BASE_OUTSIDE_RADIUS - BASE_PROFILE_MAX.x;
outer_diameter = [2*BASE_OUTSIDE_RADIUS, 2*BASE_OUTSIDE_RADIUS];
base_profile_size = size - outer_diameter;
base_bottom_size = base_profile_size + [2*translation_x, 2*translation_x];
assert(base_profile_size.x > 0 && base_profile_size.y > 0,
str("Minimum size of a single base must be greater than ", outer_diameter)
);
thumbscrew_outerdiam = 15;
thumbscrew_height = 5;
thumbscrew_tolerance = 0.4;
thumbscrew_tooth_angle = 30;
thumbscrew_pitch = 1.5;
render(convexity = 2)
difference() {
union() {
sweep_rounded(base_profile_size.x, base_profile_size.y)
translate([translation_x, 0, 0])
polygon(BASE_PROFILE);
rounded_square(
[
base_bottom_size.x + TOLLERANCE,
base_bottom_size.y + TOLLERANCE,
BASE_PROFILE_MAX.y
],
translation_x,
center=true
);
}
if (thumbscrew) {
ScrewThread(
1.01 * thumbscrew_outerdiam + 1.25 * thumbscrew_tolerance,
thumbscrew_height,
thumbscrew_pitch,
thumbscrew_tooth_angle,
thumbscrew_tolerance,
tooth_height=0
);
}
// 4 holes
// Need this fancy code to support refined holes and non-square bases.
for(a=[0:90:270]){
// i and j represent the 4 quadrants.
// The +1 is used to keep any values from being exactly 0.
j = sign(sin(a+1));
i = sign(cos(a+1));
translate([
i * (base_bottom_size.x/2 - HOLE_DISTANCE_FROM_BOTTOM_EDGE),
j * (base_bottom_size.y/2 - HOLE_DISTANCE_FROM_BOTTOM_EDGE),
0])
rotate([0, 0, a])
block_base_hole(hole_options, off);
}
}
}
/**
* @brief Stacking lip based on https://gridfinity.xyz/specification/
* @details Also includes a support base.
*/
module stacking_lip() {
polygon(STACKING_LIP);
}
/**
* @brief Stacking lip with a with a filleted (rounded) top.
* @details Based on https://gridfinity.xyz/specification/
* Also includes a support base.
*/
module stacking_lip_filleted() {
// Replace 2D edge with a radius.
// Method used: tangent, tangent, radius algorithm
// See: https://math.stackexchange.com/questions/797828/calculate-center-of-circle-tangent-to-two-lines-in-space
before_fillet = STACKING_LIP[2];
to_fillet = STACKING_LIP[3]; // tip, Point to Chamfer
after_fillet = STACKING_LIP[4];
fillet_vectors = [
to_fillet - before_fillet,
after_fillet - to_fillet,
];
to_fillet_angle = 180 + atan2(
cross(fillet_vectors[0], fillet_vectors[1]),
fillet_vectors[0] * fillet_vectors[1]
);
half_angle = to_fillet_angle / 2;
// Distance from tip to the center point of the circle.
distance_from_edge = STACKING_LIP_FILLET_RADIUS / sin(half_angle);
// Circle's center point
fillet_center_vector = distance_from_edge * [sin(half_angle), cos(half_angle)];
fillet_center_point = to_fillet - fillet_center_vector;
// Exact point edges intersect the circle
intersection_distance = fillet_center_vector.y;
// echo(final_lip_height=fillet_center_point.y + STACKING_LIP_FILLET_RADIUS);
union() {
// Rounded top
translate(concat(fillet_center_point, [0]))
circle(r = STACKING_LIP_FILLET_RADIUS);
// Stacking lip with cutout for circle to fit in
difference(){
polygon(STACKING_LIP);
translate(concat(to_fillet, [0]))
circle(r = intersection_distance);
}
}
}
/**
* @brief External wall profile, with a stacking lip.
* @details Translated so a 90 degree rotation produces the expected outside radius.
*/
module profile_wall(height_mm) {
assert(is_num(height_mm))
translate([r_base - STACKING_LIP_SIZE.x, 0, 0]){
translate([0, height_mm, 0])
stacking_lip_filleted();
translate([STACKING_LIP_SIZE.x-d_wall/2, 0, 0])
square([d_wall/2, height_mm]);
}
}
// lipless profile
module profile_wall2(height_mm) {
assert(is_num(height_mm))
translate([r_base,0,0])
mirror([1,0,0])
square([d_wall, height_mm]);
}
module block_wall(gx, gy, l) {
translate([0,0,h_base])
sweep_rounded(gx*l-2*r_base-0.5-0.001, gy*l-2*r_base-0.5-0.001)
children();
}
module block_bottom( h = 2.2, gx, gy, l ) {
translate([0,0,h_base+0.1])
rounded_rectangle(gx*l-0.5-d_wall/4, gy*l-0.5-d_wall/4, h, r_base+0.01);
}
module cut_move_unsafe(x, y, w, h) {
xx = ($gxx*l_grid+d_magic);
yy = ($gyy*l_grid+d_magic);
translate([(x)*xx/$gxx,(y)*yy/$gyy,0])
translate([(-xx+d_div)/2,(-yy+d_div)/2,0])
translate([(w*xx/$gxx-d_div)/2,(h*yy/$gyy-d_div)/2,0])
children();
}
module block_cutter(x,y,w,h,t,s,tab_width=d_tabw,tab_height=d_tabh) {
v_len_tab = tab_height;
v_len_lip = d_wall2-d_wall+1.2;
v_cut_tab = tab_height - (2*STACKING_LIP_FILLET_RADIUS)/tan(a_tab);
v_cut_lip = d_wall2-d_wall-d_clear;
v_ang_tab = a_tab;
v_ang_lip = 45;
ycutfirst = y == 0 && $style_lip == 0;
ycutlast = abs(y+h-$gyy)<0.001 && $style_lip == 0;
xcutfirst = x == 0 && $style_lip == 0;
xcutlast = abs(x+w-$gxx)<0.001 && $style_lip == 0;
zsmall = ($dh+h_base)/7 < 3;
ylen = h*($gyy*l_grid+d_magic)/$gyy-d_div;
xlen = w*($gxx*l_grid+d_magic)/$gxx-d_div;
height = $dh;
extent = (abs(s) > 0 && ycutfirst ? d_wall2-d_wall-d_clear : 0);
tab = (zsmall || t == 5) ? (ycutlast?v_len_lip:0) : v_len_tab;
ang = (zsmall || t == 5) ? (ycutlast?v_ang_lip:0) : v_ang_tab;
cut = (zsmall || t == 5) ? (ycutlast?v_cut_lip:0) : v_cut_tab;
style = (t > 1 && t < 5) ? t-3 : (x == 0 ? -1 : xcutlast ? 1 : 0);
translate([0,ylen/2,h_base+h_bot])
rotate([90,0,-90]) {
if (!zsmall && xlen - tab_width > 4*r_f2 && (t != 0 && t != 5)) {
fillet_cutter(3,"bisque")
difference() {
transform_tab(style, xlen, ((xcutfirst&&style==-1)||(xcutlast&&style==1))?v_cut_lip:0, tab_width)
translate([ycutlast?v_cut_lip:0,0])
profile_cutter(height-h_bot, ylen/2, s);
if (xcutfirst)
translate([0,0,(xlen/2-r_f2)-v_cut_lip])
cube([ylen,height,v_cut_lip*2]);
if (xcutlast)
translate([0,0,-(xlen/2-r_f2)-v_cut_lip])
cube([ylen,height,v_cut_lip*2]);
}
if (t != 0 && t != 5)
fillet_cutter(2,"indigo")
difference() {
transform_tab(style, xlen, ((xcutfirst&&style==-1)||(xcutlast&&style==1)?v_cut_lip:0), tab_width)
difference() {
intersection() {
profile_cutter(height-h_bot, ylen-extent, s);
profile_cutter_tab(height-h_bot, v_len_tab, v_ang_tab);
}
if (ycutlast) profile_cutter_tab(height-h_bot, v_len_lip, 45);
}
if (xcutfirst)
translate([ylen/2,0,xlen/2])
rotate([0,90,0])
transform_main(2*ylen)
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
if (xcutlast)
translate([ylen/2,0,-xlen/2])
rotate([0,-90,0])
transform_main(2*ylen)
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
}
}
fillet_cutter(1,"seagreen")
translate([0,0,xcutlast?v_cut_lip/2:0])
translate([0,0,xcutfirst?-v_cut_lip/2:0])
transform_main(xlen-(xcutfirst?v_cut_lip:0)-(xcutlast?v_cut_lip:0))
translate([cut,0])
profile_cutter(height-h_bot, ylen-extent-cut-(!s&&ycutfirst?v_cut_lip:0), s);
fillet_cutter(0,"hotpink")
difference() {
transform_main(xlen)
difference() {
profile_cutter(height-h_bot, ylen-extent, s);
if (!((zsmall || t == 5) && !ycutlast))
profile_cutter_tab(height-h_bot, tab, ang);
if (!(abs(s) > 0)&& y == 0)
translate([ylen-extent,0,0])
mirror([1,0,0])
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
}
if (xcutfirst)
color("indigo")
translate([ylen/2+0.001,0,xlen/2+0.001])
rotate([0,90,0])
transform_main(2*ylen)
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
if (xcutlast)
color("indigo")
translate([ylen/2+0.001,0,-xlen/2+0.001])
rotate([0,-90,0])
transform_main(2*ylen)
profile_cutter_tab(height-h_bot, v_len_lip, v_ang_lip);
}
}
}
module transform_main(xlen) {
translate([0,0,-(xlen-2*r_f2)/2])
linear_extrude(xlen-2*r_f2)
children();
}
module transform_tab(type, xlen, cut, tab_width=d_tabw) {
mirror([0,0,type==1?1:0])
copy_mirror([0,0,-(abs(type)-1)])
translate([0,0,-(xlen)/2])
translate([0,0,r_f2])
linear_extrude((xlen-tab_width-abs(cut))/(1-(abs(type)-1))-2*r_f2)
children();
}
module fillet_cutter(t = 0, c = "goldenrod") {
color(c)
minkowski() {
children();
sphere(r = r_f2-t/1000);
}
}
module profile_cutter(h, l, s) {
scoop = max(s*$dh/2-r_f2,0);
translate([r_f2,r_f2])
hull() {
if (l-scoop-2*r_f2 > 0)
square(0.1);
if (scoop < h) {
translate([l-2*r_f2,h-r_f2/2])
mirror([1,1])
square(0.1);
translate([0,h-r_f2/2])
mirror([0,1])
square(0.1);
}
difference() {
translate([l-scoop-2*r_f2, scoop])
if (scoop != 0) {
intersection() {
circle(scoop);
mirror([0,1]) square(2*scoop);
}
} else mirror([1,0]) square(0.1);
translate([l-scoop-2*r_f2,-1])
square([-(l-scoop-2*r_f2),2*h]);
translate([0,h])
square([2*l,scoop]);
}
}
}
module profile_cutter_tab(h, tab, ang) {
if (tab > 0)
color("blue")
offset(delta = r_f2)
polygon([[0,h],[tab,h],[0,h-tab*tan(ang)]]);
}