Lattice Shell

Lattice Shell

include <../config/config.scad>
UseSTL=false;
UseVitaminSTL=true;
DebugConnectors=true;
DebugCoordinateFrames=true;

LatticeShell_STL();

module LatticeShell_STL() {
	printedPart("printedparts/LatticeShell.scad", "Lattice Shell", "LatticeShell_STL()") {
	    view(t=[-2, 6, 14], r=[63, 0, 26], d=726);

        color([Level2PlasticColor[0], Level2PlasticColor[1], Level2PlasticColor[2], 1])
             if (UseSTL) {
                import(str(STLPath, "LatticeShell.stl"));
            } else {
				LatticeShell_Model();
            }
    }
}

module LatticeShell_Model() {

	sw = 2 * perim;

	or = BaseDiameter/2 + Shell_NotchTol + dw;

	sr = or - sw + eta;

	supportAngle = 50;
	bridgeDist = 5;

	numRibs1 = round(circumference(sr * cos(supportAngle)) / bridgeDist);
	numRibs = 4 * (floor(numRibs1 / 4) + 1);

	ribThickness = 0.7;

	$fn=64;

	//LatticeShell_Lattice();

	// shell with hole for LED
	//render()
	union() {
		// shell coupler and skirt
		difference() {
			rotate_extrude()
				difference() {
					union() {
						// upper coupling ring
						rotate([0,0,56])
							donutSector(
								or=or,
								ir=BaseDiameter/2 + Shell_NotchTol,
								a=20
							);

						// lower skirt
						rotate([0,0,0])
							donutSector(
								or=or,
								ir=BaseDiameter/2 + Shell_NotchTol,
								a=5
							);
					}

					polygon(
						[
							[0, ShellOpeningHeight + 1000],
							[ShellOpeningDiameter/2 + 1000, ShellOpeningHeight + 1000],
							[ShellOpeningDiameter/2 - dw - 1, ShellOpeningHeight - dw - 1],
							[0, ShellOpeningHeight - dw - 1]
						]);

				}
		}

		// Lattice
		LatticeShell_Lattice();

		// twist lock
		Shell_TwistLock();
	}

}

module LatticeShell_Lattice() {
	or = BaseDiameter/2 + Shell_NotchTol + dw;

	// number of steps in curve
	steps = 20;

	stripWidth = 2.5;
	numStrips = 40;

	intersection() {
		// shell
		rotate_extrude()
			donutSector(
				or=or,
				ir=BaseDiameter/2 + Shell_NotchTol,
				a=58
			);

		// lattice work
		for (k=[0:numStrips-1])
			rotate([0,0,k * 360/numStrips]) {
				// control points
				//cp1 = [rands(-10,10,1)[0], 0];  // start
				//cp4 = [rands(-5,5,1)[0] + (k%2 == 0 ? 20 : -20),  or * sin(60) + 5];  // end
				cp1 = [0, 0];  // start
				cp4 = [(k%2 == 0 ? 20 : -20),  or * sin(60)];  // end
				cp2 = [ cp1[0] + rands(-25,25,1)[0],  or * sin(20) ];  // handle 1
				cp3 = [ cp4[0] + rands(-25,25,1)[0],  or * sin(40)];  // handle 2

				// generate and position the curve
				rotate([90,0,0])
					translate([0,0,0])
					linear_extrude(2*or)
					union() // union lots of little "pill" shapes for each segment of the curve
					for (i=[0:steps-2]) {
						// curve time parameters for this step and next
						u1 = i / (steps-1);
						u2 = (i+1) / (steps-1);

						// 2D points for this step and next
						p1 = PtOnBez2D(cp1, cp2, cp3, cp4, u1);
						p2 = PtOnBez2D(cp1, cp2, cp3, cp4, u2);

						// hull together circles at this the position of this step and next
						hull($fn=8) {
							translate(p1) circle(stripWidth/2);
							translate(p2) circle(stripWidth/2);
						}
					}
			}
	}

}

include <../config/config.scad>
UseSTL=false;
UseVitaminSTL=true;
DebugConnectors=true;
DebugCoordinateFrames=true;

LatticeShell_STL();

module LatticeShell_STL() {
	printedPart("printedparts/LatticeShell.scad", "Lattice Shell", "LatticeShell_STL()") {
	    view(t=[-2, 6, 14], r=[63, 0, 26], d=726);

        color([Level2PlasticColor[0], Level2PlasticColor[1], Level2PlasticColor[2], 1])
             if (UseSTL) {
                import(str(STLPath, "LatticeShell.stl"));
            } else {
				LatticeShell_Model();
            }
    }
}

module LatticeShell_Model() {

	sw = 2 * perim;

	or = BaseDiameter/2 + Shell_NotchTol + dw;

	sr = or - sw + eta;

	supportAngle = 50;
	bridgeDist = 5;

	numRibs1 = round(circumference(sr * cos(supportAngle)) / bridgeDist);
	numRibs = 4 * (floor(numRibs1 / 4) + 1);

	ribThickness = 0.7;

	$fn=64;

	// shell with hole for LED
	//render()
		difference() {
			union() {
				// curved shell with centre hole for pen
				difference() {
					rotate_extrude()
						difference() {
							// outer shell
							donutSector(
								or=or,
								ir=BaseDiameter/2 + Shell_NotchTol,
								a=90
							);

							polygon(
								[
									[0, ShellOpeningHeight + 1000],
									[ShellOpeningDiameter/2 + 1000, ShellOpeningHeight + 1000],
									[ShellOpeningDiameter/2 - dw - 1, ShellOpeningHeight - dw - 1],
									[0, ShellOpeningHeight - dw - 1]
								]);

						}

					difference() {
						linear_extrude(or)
							random_voronoi(
								n = 80, nuclei = false,
								L = or+30, thickness = 1.5,
								round = 1, min = 0, max = 2*or,
								seed = 7, center = true
							);

						// hollow the middle of the voronoi mesh
						cylinder(r=ShellOpeningDiameter/2 + 5, h=or);
					}

				}

				// twist lock
				Shell_TwistLock();
			}
		}
}

/*

    Shell API Functions

    To help build loads of funky shells...  with twist-lock fitting

*/

Shell_NotchOuterWidth   = 20;
Shell_NotchSlope        = 2;
Shell_NotchInnerWidth   = Shell_NotchOuterWidth - 2* Shell_NotchSlope;
Shell_NotchDepth        = 5;
Shell_NotchTol          = 1;
Shell_NotchStop         = dw;
Shell_NotchRotation = 2 * atan2(Shell_NotchOuterWidth/2 - Shell_NotchStop/2, BaseDiameter/2);

// 2D shape to be removed from the base to support shell twist-lock
module Shell_TwistLockCutouts() {
    a = Shell_NotchOuterWidth + 2*Shell_NotchTol;
    b = Shell_NotchInnerWidth + 2*Shell_NotchTol;
    h = Shell_NotchDepth + Shell_NotchTol;
    aOffset = Shell_NotchSlope;

    for (i=[0,1])
        rotate([0,0, i*180 + Shell_NotchRotation/2])
        translate([BaseDiameter/2 - h/2 + eta, 0, 0])
        rotate([0,0, 90])
        trapezoid(b, a, h, aOffset, center=true);
}

// 3D twist-lock ring to mate a shell to the base
module Shell_TwistLock() {
    a = Shell_NotchOuterWidth;
    b = Shell_NotchInnerWidth;
    h = Shell_NotchDepth;
    aOffset = Shell_NotchSlope;

    union() {
        // Locking tabs
        for (i=[0,1])
            translate([0,0,-dw])
            difference() {
                // create the basic tab shape
                linear_extrude(dw)
                    hull() {
                        // Locking tab
                        rotate([0,0, i*180 - Shell_NotchRotation/2])
                            translate([BaseDiameter/2 - h/2 + eta, 0, 0])
                            rotate([0,0, 90])
                            trapezoid(b, a, h, aOffset, center=true);

                        // linking piece to connect locking tab to outer ring
                        translate([0,0,0])
                            rotate([0,0, i*180 - Shell_NotchRotation])
                            donutSector(or=BaseDiameter/2 + Shell_NotchTol + dw,
                                ir=BaseDiameter/2,
                                a=Shell_NotchRotation
                            );
                    }

                // now chop out a slight slope to allow for a tight friction fit
                rotate([0,0, i*180 - Shell_NotchRotation/2])
                    translate([BaseDiameter/2 - h - 3, 0, dw * 0.8])
                    rotate([5,0,0])
                    translate([0,-a/2-1,0])
                    cube([10, a + 2,10]);
            }

        // notch stops
        for (i=[0,1])
            rotate([0,0, i*180 - Shell_NotchRotation/2 + 0.3])
            translate([BaseDiameter/2 - h/2 + 1+eta, a/2 - Shell_NotchStop/2 - 0.5/2, -dw])
            rotate([0,0, 90])
            linear_extrude(dw*2)
            trapezoid(Shell_NotchStop-aOffset+0.7, Shell_NotchStop+1.3, h+2, 0, center=true);

        // outer ring
        translate([0,0,-dw])
            rotate_extrude()
            translate([BaseDiameter/2 + Shell_NotchTol, 0])
            union() {
                square([dw, 3*dw]);

                translate([eta, 3*dw - Shell_NotchTol, 0])
                    mirror([1,1,0])
                    rightTriangle(dw, dw, center = true);

                translate([-dw+eta, 3*dw - Shell_NotchTol - eta,0])
                    square([dw, Shell_NotchTol+eta]);
            }

    }
}


PrinterBotZStop.scad: initial checkin

PrinterBotZStop.scad: initial checkin

// Various Z stop parts.

BottomNutCapture(19, 14, 8, 13, 7, 3, 2);

module BottomNutCapture(width, length, height, slotWidth, nutDiameter, nutHeight, screwDiameter) {
    // width = width in mm
    // length = length in mm
    // height = height in mm

    difference() {
        // body
        cube([width, length, height], center=true);

        // slot
        translate([0, -1, 10]) {
            cube([slotWidth, length + 3, 20], center=true);
        }

        // nut
        translate([0, 0, -nutHeight]) {
            cylinder(h = nutHeight + 1, d = nutDiameter, $fn=6, center=false);
        }

        // screw hole
        cylinder(h = height + 2, d = screwDiameter, $fn=30, center=true);
    }
}

error: pathspec 'things/PrinterBotZStop.scad' did not match any file(s) known to git


Ready to laser cut...

Ready to laser cut...

width = 97; // External width (except clipping mechanism)
height = 146; // External height (except clipping mechanism)
corner_radius = 14; // Round corner at the bottom corners
top_width = 114; // Clipping mechanism width
top_height = 20; // Clipping mechanism height
thickness = 5; // Thickness
clip_height = 14;

//Main pocket
difference()
{
	union(){
		cube(size = [width,height,thickness], center = true);
		difference(){
			translate([-width/2, 0,0]) cylinder(r=20, h=thickness, center = true);
			translate([-width/2-5, 0,0]) cylinder(r=2.5, h=thickness, center = true);
			translate([-width/2-1.5, -10,0]) cube(size =[3,26,thickness], center = true);
		}
		difference(){
			translate([width/2, 0,0]) cylinder(r=20, h=thickness, center = true);
			translate([width/2+5, 0,0]) cylinder(r=2.5, h=thickness, center = true);
			translate([width/2+1.5, -10,0]) cube(size =[3,26,thickness], center = true);
}
	}
	cube(size = [width-20,height-24,thickness], center = true);
}

// Existing top clip
//translate([0, height/2 + top_height/2, 0]) cube(size = [top_width,top_height,thickness], center = true);

clip_width = width +20;
// Top clip support
difference(){
	translate([0, height/2 - clip_height/2, 0]) cube(size = [clip_width, clip_height, thickness], center = true);
	translate([-clip_width/2+7, height/2- clip_height/2, 0]) cylinder(r=2.5, h=thickness, center = true);
	translate([+clip_width/2-7, height/2- clip_height/2, 0]) cylinder(r=2.5, h=thickness, center = true);
}

// Top clip
difference()
{
	translate([-clip_height, 0 , 0]) cube(size = [clip_height, clip_width, thickness], center = true);
	translate([-clip_height, clip_width/2-7, 0]) cylinder(r=2.5, h=thickness, center = true);
	translate([-clip_height, -clip_width/2+7, 0]) cylinder(r=2.5, h=thickness, center = true);
}

// Bottom clip support
difference(){
	translate([0, -height/2+clip_height/2, 0]) cube(size = [clip_width, clip_height, thickness], center = true);
	translate([-clip_width/2+7, -height/2+ clip_height/2, 0]) cylinder(r=2.5, h=thickness, center = true);
	translate([+clip_width/2-7, -height/2+ clip_height/2, 0]) cylinder(r=2.5, h=thickness, center = true);
}
// Bottom clip
difference(){
 translate([clip_height, 0, 0]) cube(size = [clip_height, clip_width, thickness], center = true);
	translate([clip_height, clip_width/2-7, 0]) cylinder(r=2.5, h=thickness, center = true);
	translate([clip_height, -clip_width/2+7, 0]) cylinder(r=2.5, h=thickness, center = true);
}

width = 97; // External width (except clipping mechanism)
height = 142; // External height (except clipping mechanism)
corner_radius = 14; // Round corner at the bottom corners
top_width = 114; // Clipping mechanism width
top_height = 20; // Clipping mechanism height
thickness = 2*0.7; // Thickness of clipping mechanism
clip_height = 12;

//Main pocket
difference()
{
	union(){
	cube(size = [width,height,thickness], center = true);
	translate([-width/2, 0,0]) cylinder(r=20, h=thickness, center = true);
	translate([width/2, 0,0]) cylinder(r=20, h=thickness, center = true);
	}
	cube(size = [width-20,height-24,thickness], center = true);
}

// Existing top clip
//translate([0, height/2 + top_height/2, 0]) cube(size = [top_width,top_height,thickness], center = true);

clip_width = width +20;
// Top clip support
translate([0, height/2 - clip_height/2, 0]) cube(size = [clip_width, clip_height, thickness], center = true);

// Top clip
translate([-clip_height, 0 , 0]) cube(size = [clip_height, clip_width, thickness], center = true);

// Bottom clip support
translate([0, -height/2+clip_height/2, 0]) cube(size = [clip_width, clip_height, thickness], center = true);

// Bottom clip
translate([clip_height, 0, 0]) cube(size = [clip_height, clip_width, thickness], center = true);


model rotated for a print

model rotated for a print

/*
 * configuration
 */

brushes_count = 2;

/*
 * modules
 */

module lower_tub()
{
  translate([0, 20+4, 0])
    difference()
    {
      cylinder(h=10+3, r=20);
      translate([0,0,3])
        cylinder(h=10+3, r2=20, r1=16);
    }
}

module upper_ring()
{
  translate([0, 24+5, 0])
    difference()
    {
      cylinder(h=16, r=24+5);
      cylinder(h=16, r=24+0);
    }
}

module back_wall()
{
  translate([0,0,0])
  {
    // lower part
    translate([-70/2, 0, 0])
      cube([70, 3, 20]);
    // upper part
    translate([-70/2, 0, 100-16+13])
      cube([70, 3, 20]);
    // connector
    translate([-20/2, 0, 0])
      cube([20, 3, 100]);
  }
}

module brush_holder()
{
  lower_tub();
  translate([0,0,100])
    upper_ring();
  back_wall();
  // lower mounting
  difference()
  {
    translate([-30/2, 3, 0])
      cube([30, 10, 10+3]);
    translate([0, 0.1, 0]) // needed to make model simple (aka: manifold)
      hull()
        lower_tub();
  }
  // upper mounting
  translate([0, 0, 100])
  {
    difference()
    {
      translate([-40/2, 3, 0])
        cube([40, 10, 16]);
      translate([0, 0.1, 0]) // needed to make model simple (aka: manifold)
        hull()
          upper_ring();
    }
  }
}

/*
 * main program
 */

rotate([90, 0, 0])
{
  for(offset = [0:brushes_count-1])
  {
    translate(offset * [60, 0, 0])
      brush_holder();
  }
}

/*
 * configuration
 */

brushes_count = 2;

/*
 * modules
 */

module lower_tub()
{
  translate([0, 20+4, 0])
    difference()
    {
      cylinder(h=10+3, r=20);
      translate([0,0,3])
        cylinder(h=10+3, r2=20, r1=16);
    }
}

module upper_ring()
{
  translate([0, 24+5, 0])
    difference()
    {
      cylinder(h=16, r=24+5);
      cylinder(h=16, r=24+0);
    }
}

module back_wall()
{
  translate([0,0,0])
  {
    // lower part
    translate([-70/2, 0, 0])
      cube([70, 3, 20]);
    // upper part
    translate([-70/2, 0, 100-16+13])
      cube([70, 3, 20]);
    // connector
    translate([-20/2, 0, 0])
      cube([20, 3, 100]);
  }
}

module brush_holder()
{
  lower_tub();
  translate([0,0,100])
    upper_ring();
  back_wall();
  // lower mounting
  difference()
  {
    translate([-30/2, 3, 0])
      cube([30, 10, 10+3]);
    translate([0, 0.1, 0]) // needed to make model simple (aka: manifold)
      hull()
        lower_tub();
  }
  // upper mounting
  translate([0, 0, 100])
  {
    difference()
    {
      translate([-40/2, 3, 0])
        cube([40, 10, 16]);
      translate([0, 0.1, 0]) // needed to make model simple (aka: manifold)
        hull()
          upper_ring();
    }
  }
}

/*
 * main program
 */

for(offset = [0:brushes_count-1])
{
  translate(offset * [60, 0, 0])
    brush_holder();
}


The rainmaker code was refactored to parameterize more arguments.

use <../../shapes/open-cylinder/open-cylinder.scad>

module rainmaker(height = 140,
                 innerRadius = 30,
                 outerRadius = 34,
			 	 stepRadius = 2,
			 	 zRotateAngle = 25)
{
    union()
    {
        // the main shell
    	%
        openCylinder(height = height,
                     innerRadius = innerRadius,
                     outerRadius = outerRadius);

        bottomZ = 5;

        // the steps
        zStart = bottomZ;
        step_xLength = (innerRadius * 2) + 1;
        rainmaker_steps(height = height,
						stepRadius = stepRadius,
						step_xLength = step_xLength,
						zRotateAngle = zRotateAngle,
                        zStart = zStart);

        // the bottom
        cylinder(r=outerRadius, h=bottomZ);
    }
}

module rainmaker_steps(height,
						stepRadius,
						step_xLength,
						zRotateAngle,
						zStart)
{
    step = 5;

	zEnd = height - step - stepRadius;

    for(z = [zStart : step : zEnd])
    {
		xTranslate = 0;
        zTranslate = zStart + z;

		zRotate = z * zRotateAngle;

		for(s = [1 : 1 : 2])
		{
			if(s == 1)
			{
		        translate([xTranslate, 0, zTranslate])
				rotate([0, 90, zRotate])
		        cylinder(r = stepRadius,
							h = step_xLength,
							center = true);
			}
		}
    }
}

use <../../shapes/open-cylinder/open-cylinder.scad>

module rainmaker(height = 140,
                 innerRadius = 30,
                 outerRadius = 34,
			 	 stepRadius = 2)
{
    union()
    {
        // the main shell
    	%
        openCylinder(height = height,
                     innerRadius = innerRadius,
                     outerRadius = outerRadius);

        bottomZ = 5;

        // the steps
        zStart = bottomZ;
        step_xLength = (innerRadius * 2) + 1;
        rainmaker_steps(height = height,
						stepRadius = stepRadius,
						step_xLength = step_xLength,
                        zStart = zStart);

        // the bottom
        cylinder(r=outerRadius, h=bottomZ);
    }
}

module rainmaker_steps(height,
						stepRadius,
						step_xLength,
						zStart)
{
    step = 5;

	zEnd = height - step - stepRadius;

    for(z = [zStart : step : zEnd])
    {
		xTranslate = 0;
        zTranslate = zStart + z;

//		zRotate = z * 5;
		zRotate = z * 25;

		for(s = [1 : 1 : 2])
		{
			if(s == 1)
			{
//				color("green")
		        translate([xTranslate, 0, zTranslate])
				rotate([0, 90, zRotate])
		        cylinder(r = stepRadius,
							h = step_xLength,
							center = true);
			}
		}
    }
}


fix: misplacement of the elements when centering a distributed grid

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017 Jean-Sebastien CONAN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Part of the camelSCAD library.
 *
 * Operators that distribute children modules with respect to particular rules.
 *
 * @package operator/distribute
 * @author jsconan
 */

/**
 * Distributes the children modules on a grid with the provided `interval`, up to `line` elements per lines.
 *
 * @param Vector [intervalX] - The interval between each columns.
 * @param Vector [intervalY] - The interval between each lines.
 * @param Number [line] - The max number of elements per lines.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module distributeGrid(intervalX = [1, 0, 0],
                      intervalY = [0, 1, 0],
                      line      = 2,
                      center    = false) {

    intervalX = vector3D(intervalX);
    intervalY = vector3D(intervalY);
    count = $children;
    line = max(floor(abs(float(line))), 1);
    offsetX = center ? -intervalX * ((count > line ? line : count) - 1) / 2 : [0, 0, 0];
    offsetY = center ? -intervalY * (floor(count / line) - (count % line ? 0 : 1)) / 2 : [0, 0, 0];

    for (i = [0 : count - 1]) {
        translate(offsetX + intervalX * (i % line) + offsetY + intervalY * floor(i / line)) {
            children(i);
        }
    }
}

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017 Jean-Sebastien CONAN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Part of the camelSCAD library.
 *
 * Operators that distribute children modules with respect to particular rules.
 *
 * @package operator/distribute
 * @author jsconan
 */

/**
 * Distributes the children modules on a grid with the provided `interval`, up to `line` elements per lines.
 *
 * @param Vector [intervalX] - The interval between each columns.
 * @param Vector [intervalY] - The interval between each lines.
 * @param Number [line] - The max number of elements per lines.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module distributeGrid(intervalX = [1, 0, 0],
                      intervalY = [0, 1, 0],
                      line      = 2,
                      center    = false) {

    intervalX = vector3D(intervalX);
    intervalY = vector3D(intervalY);
    line = max(floor(abs(float(line))), 1);
    offsetX = center ? -intervalX * (line - 1) / 2 : [0, 0, 0];
    offsetY = center ? -intervalY * floor($children / line) / 2 : [0, 0, 0];

    for (i = [0 : $children - 1]) {
        translate(offsetX + intervalX * (i % line) + offsetY + intervalY * floor(i / line)) {
            children(i);
        }
    }
}


removed test pritn stub, to print the whole elements instead

use <m3d/fn.scad>

module oring(d_in, d_r)
{
  rotate_extrude(angle=360, convexity=20, $fn=fn(120))
    translate([d_in/2+d_r/2, 0, 0])
      circle(d=d_r, $fn=fn(40));
}

module oring_slot(d_in, d_r, d_ext, d_r_ext)
{
  rotate_extrude(angle=360, convexity=20, $fn=fn(120))
    hull()
    {
      translate([d_in/2+d_r/2, 0, 0])
        circle(d=d_r, $fn=fn(40));
      translate([d_ext/2+d_r_ext/2, 0, 0])
        circle(d=d_r_ext, $fn=fn(40));
    }
}

module nozzle()
{
  module body()
  {
    h = 15;
    cylinder(d=26.5, h=h, $fn=fn(100));
    translate([0,0,h])
    {
      h = 24.5;
      cylinder(d=32, h=h, $fn=fn(120));
      translate([0,0,h])
      {
        h = 5;
        cylinder(d1=32, d2=21, h=h, $fn=fn(200));
        translate([0,0,h])
        {
          difference()
          {
            cylinder(d=21, h=10, $fn=fn(200));
            translate([0, 0, 6])
            {
              d_in = 17-0.5;
              d_r = 2.5;
              oring_slot(d_in=d_in, d_r=d_r, d_ext=d_in+1.5, d_r_ext=d_r+0.75);
              %oring(d_in=d_in, d_r=d_r);
            }
          }
        }
      }
    }
  }

  difference()
  {
    body();
    translate([0,0,-1])
      cylinder(d=9, h=60, $fn=fn(50));
  }
}

rotate([180, 0, 0])
  nozzle();

use <m3d/fn.scad>

module oring(d_in, d_r)
{
  rotate_extrude(angle=360, convexity=20, $fn=fn(120))
    translate([d_in/2+d_r/2, 0, 0])
      circle(d=d_r, $fn=fn(40));
}

module oring_slot(d_in, d_r, d_ext, d_r_ext)
{
  rotate_extrude(angle=360, convexity=20, $fn=fn(120))
    hull()
    {
      translate([d_in/2+d_r/2, 0, 0])
        circle(d=d_r, $fn=fn(40));
      translate([d_ext/2+d_r_ext/2, 0, 0])
        circle(d=d_r_ext, $fn=fn(40));
    }
}

module nozzle()
{
  module body()
  {
    h = 15;
    cylinder(d=26.5, h=h, $fn=fn(100));
    translate([0,0,h])
    {
      h = 24.5;
      cylinder(d=32, h=h, $fn=fn(120));
      translate([0,0,h])
      {
        h = 5;
        cylinder(d1=32, d2=21, h=h, $fn=fn(200));
        translate([0,0,h])
        {
          difference()
          {
            cylinder(d=21, h=10, $fn=fn(200));
            translate([0, 0, 6])
            {
              d_in = 17-0.5;
              d_r = 2.5;
              oring_slot(d_in=d_in, d_r=d_r, d_ext=d_in+1.5, d_r_ext=d_r+0.75);
              %oring(d_in=d_in, d_r=d_r);
            }
          }
        }
      }
    }
  }

  difference()
  {
    body();
    translate([0,0,-1])
      cylinder(d=9, h=60, $fn=fn(50));
  }
}

//rotate([180, 0, 0])
intersection()
{
  translate([0, 0, 54.5])
  cube([50, 50, 20], center=true);
  nozzle();
}

//oring_slot(d_in=17-0.25, d_r=2.5, d_ext=20, d_r_ext=3);