discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Plumbing!

JB
Jordan Brown
Fri, Jul 24, 2020 12:08 AM

So it's past time to overhaul the sprinklers in the front yard, and that
involved building a new sprinkler manifold.

A normal person would sketch out something, go buy some parts, and start
putting them together.  But that would be too easy!

Besides, I kept losing track of how many of each part I needed.

OpenSCAD to the rescue!

That's a sprinkler manifold, with automatic valves.

Once I got the components built, describing the plumbing was easy.  You
start with the source of the water, and for each component the
downstream components are its children.  Thus, e.g.:

pipe(length=100) elbow() pipe(length=100);

yields:

Tees are a little trickier, but not much:  they have two children.

Note that you can only really model tree structures (like real
plumbing); there's no mechanism for figuring out how to reconnect to
previously defined plumbing.

This is not product-grade, but it let me "draw" my project and produce a
BOM.

The components come first; a couple of manifold models are at the end. 
(Note that only plan2() is rendered; plan1() is an earlier idea that I
didn't end up using.)

// Plumbing modeling
// Make it "easy" to build plumbing assemblies.
// Jordan Brown 23 July 2020 openscad@jordan.maileater.net

// CAVEAT:  This was built for visualization and parts lists only.
// Dimensions are approximate and are sometimes guesses.
// Do not use where precise scale is required!

// In addition to the model, this produces a bill of materials, like
// so:
//     ECHO: "connector", "M", "S", "3/4"", "sch40"
//     ECHO: "elbow", "S", "S", "3/4"", "sch40"
//     ECHO: "angle45", "S", "S", "3/4"", "sch40"

// See the example at the bottom for how to use these modules.
// Yeah, it should be in a library.  Some day.

// Note:  It seems like I should specify what the orientation is for
// each connector - whether an elbow turns "left" or "right".
// But as soon as you get started, it hurts my head to even figure out
// what "left" and "right" mean, so I haven't bothered.  Just add a
// part and insert whatever rotation is needed.  Note that since the
// parts always start from [0,0,0] and extend into +Z, you only
// ever need a single-term rotate.  (Which makes sense; the only
// rotation you can control is how the next piece attaches.)
// Except, of course, for the first part, where you need to rotate
// and perhaps translate so as to start at the right place and heading
// the right direction.

// Indexes for connection types.
SLIP=0;
PIPE=1;
MIPT=2;
FIPT=3;
labels = [ "S", "P", "M", "F" ];

// Indexes for PVC pipe sizes.
S12 = 0;    // 1/2"
S34 = 1;    // 3/4"
S1 = 2;     // 1"
slabels = [ "1/2\"", "3/4\"", "1\"" ];

// Following dimensions are a mix of guesses and measurement.

// *_depth, *_length, *_od are indexed by the pipe size.
// depths, lengths, and ods are indexed by the type of connection.

// Depths - how far the adjacent piece slides inside this one.
slip_depth = [ 20, 20, 20 ];
mipt_depth = [ 0, 0, 0 ];
fipt_depth = [ 11, 11, 11 ];
depths = [ slip_depth, 0, mipt_depth, fipt_depth ];

// Lengths - how long the "joint" part of the component is.
slip_length = [ 20,20,20 ];
mipt_length = [ 17,17,17 ];
fipt_length = [ 20,20,20 ];
lengths = [ slip_length, 0, mipt_length, fipt_length ];

// Outside diameters of the various connectors.
slip_od = [ 18, 33, 41 ];
pipe_od = [ 15, 27, 33 ];
mipt_od = [ 16, 26, 28 ];
fipt_od = [ 18, 33, 41 ];
ods = [ slip_od, pipe_od, mipt_od, fipt_od ];

// Theoretically there could also be a list of inside diameters,
// but at the moment I'm not modeling the insides.

// Outside diameter of hosebib body.  The rest of the hosebib
// dimensions are hardcoded.
hosebib_ods = [ undef, 29, undef ];

// Following are dimensions of various parts of a Champion valve and
// compact automatic actuator, for 3/4" and 1".  Right now they're the
// same, which is close but not quite right.
//
//    ball_d = dims[0];
//    inlet_od = dims[1];
//    body = dims[2];
//    union_od = dims[3];
//    union_h = dims[4];
//    cap_d = dims[5];
//    cap_h = dims[6];
//    neck_h = dims[7];
//    neck_d = dims[8];
//    saucer_h = dims[9];
//    saucer_d = dims[10];
//    screw_h = dims[11];
//    screw_d = dims[12];
//    sol_x = dims[13];
//    sol_h = dims[14];
//    sol_d = dims[15];
//    c_to_c = dims[16];    // spacing between centers of input and output
//    union_h2 = dims[17];
//    union_d2 = dims[18]; //32
//    cap_x = dims[19];
//    sep = dims[20];       // Separation between adjacent valves

spr_valve_dims = [
    undef, // No 1/2" valves
    [50, 41, [120, 30, 50], 52, 20, 52, 10, 25, 18, 20, 80, 50, 10, 25, 30, 25, 65, 16, 32, 50, 100 ],        // approx
    [50, 41, [120, 30, 50], 52, 20, 52, 10, 25, 18, 20, 80, 50, 10, 25, 30, 25, 65, 16, 32, 50, 100 ],        // approx
];

// This is a single connector, either a slip-female, a thread-male,
// or a thread-female.
// Its [0,0,0] is where the previous part ends,
// so for either of the female types it's inside the part, where for
// the thread-male it's at the end.
// It optionally takes a child, which is any subsequent part, similarly
// positioned.
// end() should probably take the rest of the component as a child,
// so that end() can be responsible for positioning the rest of the
// component, but it doesn't; the component is responsible for that
// positioning.
// Includes a label on the connector saying what type it is.
module end(size=S34, type=SLIP) {
    h = lengths[type][size];
    d = ods[type][size];
    // Switch here so we can render differently in the future.
    if (type == SLIP) {
        #cylinder(h=h, d=d);
    } else if (type == MIPT) {
        cylinder(h=h, d=d);
    } else if (type == FIPT) {
        #cylinder(h=h, d=d);
    } else {
        assert(false);
    }
    translate([0,0,lengths[type][size] - depths[type][size]])
        children();
    %for (r = [0,180]) {
        rotate(r)
            translate([0, -d/2, h/2])
            rotate([90,90,0])
            linear_extrude(1)
            text(labels[type], halign="center", valign="center", size=10);
    }
}

// Now we get into the production buy-at-the-store components.
// The default size is 3/4", the default type of connection is PVC
// slip, and the default is schedule 40.  (The schedule is used
// only for the BOM, though in the future it might be used to model
// wall thickness.)

// This is a straight-through connector.  Because you can specify the
// type of each end, you can model slip-slip, slip-MIPT, et cetera.
// Size is the size of the part.  It should probably be split into
// size1 and size2 to allow for size-conversion connectors.
// Type1 is the type of the "previous" connection; type2 is the
// type of the "next" connection.
module connector(size=S34, type1=SLIP, type2=SLIP, sched=40) {
    echo("connector", labels[type1], labels[type2], slabels[size], str("sch", sched));
    translate([0,0,-depths[type1][size]]) {
        end(size=size, type=type1);
        h = lengths[type1][size];
        translate([0,0,h]) {
            end(size=size, type=type2)
                children();
        }
    }
}

// A 45 degree angle.  Note that I didn't bother to model the actual
// joint in the middle; I should throw a sphere in there.
module angle45(size=S34, type1=SLIP, type2=SLIP, sched=40) {
    echo("angle45", labels[type1], labels[type2], slabels[size], str("sch", sched));
    translate([0,0,-depths[type1][size]]) {
        end(size=size, type=type1);
        h = lengths[type1][size];
        translate([0,0,h]) {
            rotate([0,45,0]) end(size=size, type=type2)
                children();
        }
    }
}

// A 90 degree elbow.
// size controls the size of the part (and should be split into size1
// and size2).
// type1 controls the "previous" type; type2 controls the "next" type.
module elbow(size=S34, type1=SLIP, type2=SLIP, sched=40) {
    echo("elbow", labels[type1], labels[type2], slabels[size], str("sch", sched));
    translate([0,0,-depths[type1][size]]) {
        end(size=size, type=type1);
        translate([0,0,lengths[type1][size]]) {
            cylinder(h=ods[SLIP][size]/2, d=ods[SLIP][size]);
            translate([0,0,ods[type2][size]/2]) {
                sphere(d=ods[SLIP][size]);
                rotate([0,90,0]) {
                    cylinder(h=ods[SLIP][size]/2, d=ods[SLIP][size]);
                    translate([0,0,ods[type1][size]/2]) {
                        end(size=size, type=type2)
                            children();
                    }
                }
            }
        }
    }
}

// A three-way corner.  size1 and type1 are for the "previous"
// connection.  If you imagine looking into the part horizontally,
// with one of the other two connectors pointing left and the other
// pointing down, size2 and type2 point left and size3 and type3
// point down.
module corner(size1=S34, size2=S34, size3=S34, type1=SLIP, type2=SLIP, type3=SLIP, sched=40) {
    if (size1 != size2 || size2 != size3 || type1 != type2 || type2 != type3) {
        echo("WARNING:  corner() does not align mixed-type parts correctly.");
    }
    echo("corner", labels[type1], labels[type2], labels[type3], slabels[size1], slabels[size2], slabels[size3], str("sch", sched));
    translate([0,0,-depths[type1][size1]]) {
        end(size=size1, type=type1);
        translate([0,0,lengths[type1][size1]]) {
            cylinder(h=ods[SLIP][size1]/2, d=ods[SLIP][size1]);
            translate([0,0,ods[type1][size1]/2]) {
                rotate([0,90,0]) {
                    cylinder(h=ods[SLIP][size1]/2, d=ods[SLIP][size1]);
                    translate([0,0,ods[SLIP][size2]/2])
                        end(size=size2, type=type2)
                        if ($children > 1) children(0);
                }
                rotate([90,0,0]) {
                    cylinder(h=ods[SLIP][size3]/2, d=ods[SLIP][size3]);
                    translate([0,0,ods[SLIP][size3]/2])
                        end(size=size3, type=type3)
                        if ($children > 0) children(1);
                }
            }
        }
    }
}

// A tee connector, approached along the top of the T.
// size and type control the body, and size3 and type3 control the
// body of the T.  Probably size and type should split into size1/2 and
// type1/2, though real-world connectors don't have that many options.
module tee(size=S34, size3=S34, type=SLIP, type3=SLIP, sched=40) {
    echo("tee", labels[type], labels[type3], slabels[size], slabels[size3], str("sch", sched));
    translate([0,0,-depths[type][size]]) {
        end(size=size, type=type);
        translate([0,0,depths[type][size]]) {
            translate([0,0,ods[type3/2][size]/2]) {
                rotate([0,90,0]) cylinder(h=ods[SLIP][size]/2, d=ods[SLIP][size]);
                translate([ods[SLIP][size]/2,0,0])
                    rotate([0,90,0])
                    end(size=size3, type=type3)
                    if ($children > 1) children(1);
            }
            cylinder(h=ods[type3][size3], d=ods[SLIP][size]);
            translate([0,0,ods[type3][size3]])
                end(size=size, type=type)
                if ($children > 0) children(0);
        }
    }
}

// A tee, approached from the body of the tee.
// As for tee(), size3 and type3 refer to the body of the T,
// even though here that's the direction we're approaching from.
module tee2(size=S34, size3=S34, type=SLIP, type3=SLIP, sched=40) {
    echo("tee", labels[type], labels[type3], slabels[size], slabels[size3], str("sch", sched));
    translate([0,0,-depths[type3][size3]]) {
        end(size=size3, type=type3);
        translate([0,0,lengths[type3][size3]]) {
            cylinder(h=ods[SLIP][size]/2, d=ods[SLIP][size]);
            translate([0,0,ods[type][size]/2]) {
                rotate([0,90,0]) cylinder(h=ods[SLIP][size], d=ods[SLIP][size], center=true);
                rotate([0,90,0])
                    translate([0,0,ods[SLIP][size]/2])
                    end(size=size, type=type)
                    if ($children > 1) children(1);
                rotate([0,-90,0])
                    translate([0,0,ods[SLIP][size]/2])
                    end(size=size, type=type)
                    if ($children > 0) children(0);
            }
        }
    }
}

// A hose bib.  Very approximate.
module hosebib(size=S34, type=MIPT) {
    echo("hosebib", labels[type], slabels[size]);
    translate([0,0,depths[type][size]]) {
        end(size=size, type=type);
        translate([0,0,lengths[type][size]]) {
            cylinder(h=20, d=hosebib_ods[size]);
            translate([0,0,20]) {
                rotate([30,0,0]) cylinder(h=33, d=26);
                rotate([-60,0,0]) cylinder(h=45, d=25);
                translate([0,40,25]) rotate([0,90,0]) cylinder(h=60,d=11,center=true);
            }
        }
    }
}

// A pipe.
// You can either use a size index or you can directly specify
// a diameter.  Directly specifying a diameter was a hackish way
// to create a generic unmodeled part.  You can specify a label,
// for generic unmodeled parts.
module pipe(size=S34, d, length, label) {
    d = d == undef ? ods[PIPE][size] : d;
    cylinder(h=length, d=d);
    if (label != undef) {
        %for (a = [0,180]) {
            rotate(a)
                translate([0, -d/2, length/2])
                rotate([90,90,0])
                linear_extrude(1)
                text(label, halign="center", valign="center", size=10);
        }
    }
    translate([0,0,length]) children();
}


// A function to abstract a couple of externally-important
// dimensions of a sprinkler valve.
function sprinkler_valve_dims(size=S34) =
    let(dims=spr_valve_dims[size])
    [
        dims[16],   // c-to-c
        dims[17],    // union_h2
        dims[20]    // sep
    ];

// A Champion valve body and compact automatic actuator.
// I don't know whether the input and output can be different connector
// types; the ones I have are both FIPT.
// Note that this is a connector; you connect to its input side and
// the next component is attached to its output side.
module sprinkler_valve(size=S34, type=FIPT) {
    dims = spr_valve_dims[size];
    ball_d = dims[0];
    inlet_od = dims[1];
    body = dims[2];
    union_od = dims[3];
    union_h = dims[4];
    cap_d = dims[5];
    cap_h = dims[6];
    neck_h = dims[7];
    neck_d = dims[8];
    saucer_h = dims[9];
    saucer_d = dims[10];
    screw_h = dims[11];
    screw_d = dims[12];
    sol_x = dims[13];
    sol_h = dims[14];
    sol_d = dims[15];
    c_to_c = dims[16];
    union_h2 = dims[17];
    union_d2 = dims[18]; //32
    cap_x = dims[19];
    sep = dims[20];
    
    body_z = 15;
    neck_z = body_z + body.z;

    translate([0,0,-depths[type][size]]) {
        // Should use end().
        cylinder(h=30, d=inlet_od, $fn=6);
        translate([0,0,10 + ball_d/2]) sphere(d=ball_d);
        translate([0,0,neck_z - 30]) cylinder(h=30, d=inlet_od);
        translate([0,-body.y/2,body_z]) cube([c_to_c+body.y/2, body.y, body.z]);
        out_center_x = body.x-ball_d/2-body.y/2;
        translate([c_to_c,0,0]) cylinder(h=union_h,d=union_od);
        translate([c_to_c,0,0]) rotate([180,0,0]) {
            // Should use end().
            cylinder(h=union_h2, d=union_d2);
            translate([0,0,union_h2-depths[type][size]])
                children();
        }
        translate([cap_x,0,body_z+body.z]) cylinder(h=cap_h, d=cap_d, $fn=6);
        translate([0,0,neck_z]) cylinder(h=neck_h, d=neck_d, $fn=6);
        saucer_z = neck_z + neck_h;
        translate([0,0,saucer_z]) cylinder(h=saucer_h, d=saucer_d);
        screw_z = saucer_z + saucer_h;
        translate([0,0,screw_z]) cylinder(h=screw_h, d=screw_d);
        translate([sol_x,0,screw_z]) cylinder(h=sol_h, d=sol_d);
    }
}

// A not-very-completely-modeled pressure regulator.
// Connectors not really modeled right.
function reg_dims(size=S34) = [[ 134,70,136 ], 30];

module regulator(size=S34, type=FIPT) {
    dims = reg_dims();
    box = dims[0];
    c_z = dims[1];
    translate([0,0,-depths[type][size]]) {
        translate([-c_z,-box.y/2,0]) {
            cube([box.z,box.y,box.x]);
        }
        translate([0,0,box.x-depths[type][size]])
            children();
    }
}


//
// And now on to the actual plumbing project.
//
// Some dimensions:  how high I want the valves off
// the ground, how deep I want the bottom of the manifold
// buried, how high the regulator should be, separation
// between valves.
//
// +X is east
// +Y is north

// Eventually I want to have an exploded view.  But not yet.
explode = false;

valve_z = 280;
manifold_depth = 200;
reg_z = valve_z;
sv_sep = sprinkler_valve_dims()[2];

// Variation 1:  with pressure regulator.
// I didn't end up using this variation.
module plan1() {
    pipe(length=reg_z-50)
        connector()
        pipe(length=50)
        rotate(180) elbow(type1=SLIP, type2=MIPT, sched=80)
        rotate(180) regulator()
        rotate(180) elbow(type1=MIPT, type2=SLIP)
        pipe(length=50)
        rotate(180) tee(size3=S34, type3=FIPT) {
            pipe(length=manifold_depth + reg_z - 80)
            rotate(-90) elbow()
                pipe(length=sv_sep)
                tee() {
                    pipe(length=sv_sep)
                        elbow()
                        pipe(length=valve_z + manifold_depth)
                        rotate(-90) sprinkler_valve_assy()
                        pipe(length=valve_z);
                    pipe(length=valve_z + manifold_depth)
                        rotate(-90) sprinkler_valve_assy()
                        pipe(length=valve_z);
                }
            rotate(-90) hosebib();
        }
}

// Variation 2:  Starting downstream of existing regulator,
// at a different point in the system.
// This is what I ended up building.
module plan2() {
    rotate([180,0,0])
        connector(type1=MIPT, size=S1)
        pipe(length=50, size=S1)
        rotate(90) elbow(size=S1)
        pipe(length=100, size=S1)
        corner(size1=S1,size2=S1) {
            pipe(length=400, size=S1)
                rotate(180) sprinkler_valve_assy(size=S1);
            pipe(length=sv_sep)
                tee() {
                    pipe(length=sv_sep)
                        elbow()
                        pipe(length=300)
                        rotate(180) tee(type3=FIPT) {
                            pipe(length=70)
                                rotate(-90) sprinkler_valve_assy()
                                    pipe(length=400)
                                    rotate(-90) elbow()
                                    pipe(length=1450)
                                    rotate(90) angle45()
                                    pipe(length=1000);
                            rotate(90) hosebib();
                        }
                    pipe(length=400)
                        rotate(90) sprinkler_valve_assy()
                        pipe(length=430)
                        rotate(-90) elbow()
                        pipe(length=1500)
                        rotate(90) angle45()
                        pipe(length=1000);
                }
        };
}

plan2();

// A sprinkler valve assembly, with the two PVC connectors attached.
// Note that this is a connector; the input side connects to the
// previous component and the child is the output side.
module sprinkler_valve_assy(size=S34) {
        connector(type1=SLIP, type2=MIPT, size=size)
        sprinkler_valve(size=size)
        connector(type1=MIPT, type2=SLIP, size=size)
        children();
}
So it's past time to overhaul the sprinklers in the front yard, and that involved building a new sprinkler manifold. A normal person would sketch out something, go buy some parts, and start putting them together.  But that would be too easy! Besides, I kept losing track of how many of each part I needed. OpenSCAD to the rescue! That's a sprinkler manifold, with automatic valves. Once I got the components built, describing the plumbing was easy.  You start with the source of the water, and for each component the downstream components are its children.  Thus, e.g.: pipe(length=100) elbow() pipe(length=100); yields: Tees are a little trickier, but not much:  they have two children. Note that you can only really model tree structures (like real plumbing); there's no mechanism for figuring out how to reconnect to previously defined plumbing. This is not product-grade, but it let me "draw" my project and produce a BOM. The components come first; a couple of manifold models are at the end.  (Note that only plan2() is rendered; plan1() is an earlier idea that I didn't end up using.) // Plumbing modeling // Make it "easy" to build plumbing assemblies. // Jordan Brown 23 July 2020 openscad@jordan.maileater.net // CAVEAT: This was built for visualization and parts lists only. // Dimensions are approximate and are sometimes guesses. // Do not use where precise scale is required! // In addition to the model, this produces a bill of materials, like // so: // ECHO: "connector", "M", "S", "3/4"", "sch40" // ECHO: "elbow", "S", "S", "3/4"", "sch40" // ECHO: "angle45", "S", "S", "3/4"", "sch40" // See the example at the bottom for how to use these modules. // Yeah, it should be in a library. Some day. // Note: It seems like I should specify what the orientation is for // each connector - whether an elbow turns "left" or "right". // But as soon as you get started, it hurts my head to even figure out // what "left" and "right" mean, so I haven't bothered. Just add a // part and insert whatever rotation is needed. Note that since the // parts always start from [0,0,0] and extend into +Z, you only // ever need a single-term rotate. (Which makes sense; the only // rotation you can control is how the next piece attaches.) // Except, of course, for the first part, where you need to rotate // and perhaps translate so as to start at the right place and heading // the right direction. // Indexes for connection types. SLIP=0; PIPE=1; MIPT=2; FIPT=3; labels = [ "S", "P", "M", "F" ]; // Indexes for PVC pipe sizes. S12 = 0; // 1/2" S34 = 1; // 3/4" S1 = 2; // 1" slabels = [ "1/2\"", "3/4\"", "1\"" ]; // Following dimensions are a mix of guesses and measurement. // *_depth, *_length, *_od are indexed by the pipe size. // depths, lengths, and ods are indexed by the type of connection. // Depths - how far the adjacent piece slides inside this one. slip_depth = [ 20, 20, 20 ]; mipt_depth = [ 0, 0, 0 ]; fipt_depth = [ 11, 11, 11 ]; depths = [ slip_depth, 0, mipt_depth, fipt_depth ]; // Lengths - how long the "joint" part of the component is. slip_length = [ 20,20,20 ]; mipt_length = [ 17,17,17 ]; fipt_length = [ 20,20,20 ]; lengths = [ slip_length, 0, mipt_length, fipt_length ]; // Outside diameters of the various connectors. slip_od = [ 18, 33, 41 ]; pipe_od = [ 15, 27, 33 ]; mipt_od = [ 16, 26, 28 ]; fipt_od = [ 18, 33, 41 ]; ods = [ slip_od, pipe_od, mipt_od, fipt_od ]; // Theoretically there could also be a list of inside diameters, // but at the moment I'm not modeling the insides. // Outside diameter of hosebib body. The rest of the hosebib // dimensions are hardcoded. hosebib_ods = [ undef, 29, undef ]; // Following are dimensions of various parts of a Champion valve and // compact automatic actuator, for 3/4" and 1". Right now they're the // same, which is close but not quite right. // // ball_d = dims[0]; // inlet_od = dims[1]; // body = dims[2]; // union_od = dims[3]; // union_h = dims[4]; // cap_d = dims[5]; // cap_h = dims[6]; // neck_h = dims[7]; // neck_d = dims[8]; // saucer_h = dims[9]; // saucer_d = dims[10]; // screw_h = dims[11]; // screw_d = dims[12]; // sol_x = dims[13]; // sol_h = dims[14]; // sol_d = dims[15]; // c_to_c = dims[16]; // spacing between centers of input and output // union_h2 = dims[17]; // union_d2 = dims[18]; //32 // cap_x = dims[19]; // sep = dims[20]; // Separation between adjacent valves spr_valve_dims = [ undef, // No 1/2" valves [50, 41, [120, 30, 50], 52, 20, 52, 10, 25, 18, 20, 80, 50, 10, 25, 30, 25, 65, 16, 32, 50, 100 ], // approx [50, 41, [120, 30, 50], 52, 20, 52, 10, 25, 18, 20, 80, 50, 10, 25, 30, 25, 65, 16, 32, 50, 100 ], // approx ]; // This is a single connector, either a slip-female, a thread-male, // or a thread-female. // Its [0,0,0] is where the previous part ends, // so for either of the female types it's inside the part, where for // the thread-male it's at the end. // It optionally takes a child, which is any subsequent part, similarly // positioned. // end() should probably take the rest of the component as a child, // so that end() can be responsible for positioning the rest of the // component, but it doesn't; the component is responsible for that // positioning. // Includes a label on the connector saying what type it is. module end(size=S34, type=SLIP) { h = lengths[type][size]; d = ods[type][size]; // Switch here so we can render differently in the future. if (type == SLIP) { #cylinder(h=h, d=d); } else if (type == MIPT) { cylinder(h=h, d=d); } else if (type == FIPT) { #cylinder(h=h, d=d); } else { assert(false); } translate([0,0,lengths[type][size] - depths[type][size]]) children(); %for (r = [0,180]) { rotate(r) translate([0, -d/2, h/2]) rotate([90,90,0]) linear_extrude(1) text(labels[type], halign="center", valign="center", size=10); } } // Now we get into the production buy-at-the-store components. // The default size is 3/4", the default type of connection is PVC // slip, and the default is schedule 40. (The schedule is used // only for the BOM, though in the future it might be used to model // wall thickness.) // This is a straight-through connector. Because you can specify the // type of each end, you can model slip-slip, slip-MIPT, et cetera. // Size is the size of the part. It should probably be split into // size1 and size2 to allow for size-conversion connectors. // Type1 is the type of the "previous" connection; type2 is the // type of the "next" connection. module connector(size=S34, type1=SLIP, type2=SLIP, sched=40) { echo("connector", labels[type1], labels[type2], slabels[size], str("sch", sched)); translate([0,0,-depths[type1][size]]) { end(size=size, type=type1); h = lengths[type1][size]; translate([0,0,h]) { end(size=size, type=type2) children(); } } } // A 45 degree angle. Note that I didn't bother to model the actual // joint in the middle; I should throw a sphere in there. module angle45(size=S34, type1=SLIP, type2=SLIP, sched=40) { echo("angle45", labels[type1], labels[type2], slabels[size], str("sch", sched)); translate([0,0,-depths[type1][size]]) { end(size=size, type=type1); h = lengths[type1][size]; translate([0,0,h]) { rotate([0,45,0]) end(size=size, type=type2) children(); } } } // A 90 degree elbow. // size controls the size of the part (and should be split into size1 // and size2). // type1 controls the "previous" type; type2 controls the "next" type. module elbow(size=S34, type1=SLIP, type2=SLIP, sched=40) { echo("elbow", labels[type1], labels[type2], slabels[size], str("sch", sched)); translate([0,0,-depths[type1][size]]) { end(size=size, type=type1); translate([0,0,lengths[type1][size]]) { cylinder(h=ods[SLIP][size]/2, d=ods[SLIP][size]); translate([0,0,ods[type2][size]/2]) { sphere(d=ods[SLIP][size]); rotate([0,90,0]) { cylinder(h=ods[SLIP][size]/2, d=ods[SLIP][size]); translate([0,0,ods[type1][size]/2]) { end(size=size, type=type2) children(); } } } } } } // A three-way corner. size1 and type1 are for the "previous" // connection. If you imagine looking into the part horizontally, // with one of the other two connectors pointing left and the other // pointing down, size2 and type2 point left and size3 and type3 // point down. module corner(size1=S34, size2=S34, size3=S34, type1=SLIP, type2=SLIP, type3=SLIP, sched=40) { if (size1 != size2 || size2 != size3 || type1 != type2 || type2 != type3) { echo("WARNING: corner() does not align mixed-type parts correctly."); } echo("corner", labels[type1], labels[type2], labels[type3], slabels[size1], slabels[size2], slabels[size3], str("sch", sched)); translate([0,0,-depths[type1][size1]]) { end(size=size1, type=type1); translate([0,0,lengths[type1][size1]]) { cylinder(h=ods[SLIP][size1]/2, d=ods[SLIP][size1]); translate([0,0,ods[type1][size1]/2]) { rotate([0,90,0]) { cylinder(h=ods[SLIP][size1]/2, d=ods[SLIP][size1]); translate([0,0,ods[SLIP][size2]/2]) end(size=size2, type=type2) if ($children > 1) children(0); } rotate([90,0,0]) { cylinder(h=ods[SLIP][size3]/2, d=ods[SLIP][size3]); translate([0,0,ods[SLIP][size3]/2]) end(size=size3, type=type3) if ($children > 0) children(1); } } } } } // A tee connector, approached along the top of the T. // size and type control the body, and size3 and type3 control the // body of the T. Probably size and type should split into size1/2 and // type1/2, though real-world connectors don't have that many options. module tee(size=S34, size3=S34, type=SLIP, type3=SLIP, sched=40) { echo("tee", labels[type], labels[type3], slabels[size], slabels[size3], str("sch", sched)); translate([0,0,-depths[type][size]]) { end(size=size, type=type); translate([0,0,depths[type][size]]) { translate([0,0,ods[type3/2][size]/2]) { rotate([0,90,0]) cylinder(h=ods[SLIP][size]/2, d=ods[SLIP][size]); translate([ods[SLIP][size]/2,0,0]) rotate([0,90,0]) end(size=size3, type=type3) if ($children > 1) children(1); } cylinder(h=ods[type3][size3], d=ods[SLIP][size]); translate([0,0,ods[type3][size3]]) end(size=size, type=type) if ($children > 0) children(0); } } } // A tee, approached from the body of the tee. // As for tee(), size3 and type3 refer to the body of the T, // even though here that's the direction we're approaching from. module tee2(size=S34, size3=S34, type=SLIP, type3=SLIP, sched=40) { echo("tee", labels[type], labels[type3], slabels[size], slabels[size3], str("sch", sched)); translate([0,0,-depths[type3][size3]]) { end(size=size3, type=type3); translate([0,0,lengths[type3][size3]]) { cylinder(h=ods[SLIP][size]/2, d=ods[SLIP][size]); translate([0,0,ods[type][size]/2]) { rotate([0,90,0]) cylinder(h=ods[SLIP][size], d=ods[SLIP][size], center=true); rotate([0,90,0]) translate([0,0,ods[SLIP][size]/2]) end(size=size, type=type) if ($children > 1) children(1); rotate([0,-90,0]) translate([0,0,ods[SLIP][size]/2]) end(size=size, type=type) if ($children > 0) children(0); } } } } // A hose bib. Very approximate. module hosebib(size=S34, type=MIPT) { echo("hosebib", labels[type], slabels[size]); translate([0,0,depths[type][size]]) { end(size=size, type=type); translate([0,0,lengths[type][size]]) { cylinder(h=20, d=hosebib_ods[size]); translate([0,0,20]) { rotate([30,0,0]) cylinder(h=33, d=26); rotate([-60,0,0]) cylinder(h=45, d=25); translate([0,40,25]) rotate([0,90,0]) cylinder(h=60,d=11,center=true); } } } } // A pipe. // You can either use a size index or you can directly specify // a diameter. Directly specifying a diameter was a hackish way // to create a generic unmodeled part. You can specify a label, // for generic unmodeled parts. module pipe(size=S34, d, length, label) { d = d == undef ? ods[PIPE][size] : d; cylinder(h=length, d=d); if (label != undef) { %for (a = [0,180]) { rotate(a) translate([0, -d/2, length/2]) rotate([90,90,0]) linear_extrude(1) text(label, halign="center", valign="center", size=10); } } translate([0,0,length]) children(); } // A function to abstract a couple of externally-important // dimensions of a sprinkler valve. function sprinkler_valve_dims(size=S34) = let(dims=spr_valve_dims[size]) [ dims[16], // c-to-c dims[17], // union_h2 dims[20] // sep ]; // A Champion valve body and compact automatic actuator. // I don't know whether the input and output can be different connector // types; the ones I have are both FIPT. // Note that this is a connector; you connect to its input side and // the next component is attached to its output side. module sprinkler_valve(size=S34, type=FIPT) { dims = spr_valve_dims[size]; ball_d = dims[0]; inlet_od = dims[1]; body = dims[2]; union_od = dims[3]; union_h = dims[4]; cap_d = dims[5]; cap_h = dims[6]; neck_h = dims[7]; neck_d = dims[8]; saucer_h = dims[9]; saucer_d = dims[10]; screw_h = dims[11]; screw_d = dims[12]; sol_x = dims[13]; sol_h = dims[14]; sol_d = dims[15]; c_to_c = dims[16]; union_h2 = dims[17]; union_d2 = dims[18]; //32 cap_x = dims[19]; sep = dims[20]; body_z = 15; neck_z = body_z + body.z; translate([0,0,-depths[type][size]]) { // Should use end(). cylinder(h=30, d=inlet_od, $fn=6); translate([0,0,10 + ball_d/2]) sphere(d=ball_d); translate([0,0,neck_z - 30]) cylinder(h=30, d=inlet_od); translate([0,-body.y/2,body_z]) cube([c_to_c+body.y/2, body.y, body.z]); out_center_x = body.x-ball_d/2-body.y/2; translate([c_to_c,0,0]) cylinder(h=union_h,d=union_od); translate([c_to_c,0,0]) rotate([180,0,0]) { // Should use end(). cylinder(h=union_h2, d=union_d2); translate([0,0,union_h2-depths[type][size]]) children(); } translate([cap_x,0,body_z+body.z]) cylinder(h=cap_h, d=cap_d, $fn=6); translate([0,0,neck_z]) cylinder(h=neck_h, d=neck_d, $fn=6); saucer_z = neck_z + neck_h; translate([0,0,saucer_z]) cylinder(h=saucer_h, d=saucer_d); screw_z = saucer_z + saucer_h; translate([0,0,screw_z]) cylinder(h=screw_h, d=screw_d); translate([sol_x,0,screw_z]) cylinder(h=sol_h, d=sol_d); } } // A not-very-completely-modeled pressure regulator. // Connectors not really modeled right. function reg_dims(size=S34) = [[ 134,70,136 ], 30]; module regulator(size=S34, type=FIPT) { dims = reg_dims(); box = dims[0]; c_z = dims[1]; translate([0,0,-depths[type][size]]) { translate([-c_z,-box.y/2,0]) { cube([box.z,box.y,box.x]); } translate([0,0,box.x-depths[type][size]]) children(); } } // // And now on to the actual plumbing project. // // Some dimensions: how high I want the valves off // the ground, how deep I want the bottom of the manifold // buried, how high the regulator should be, separation // between valves. // // +X is east // +Y is north // Eventually I want to have an exploded view. But not yet. explode = false; valve_z = 280; manifold_depth = 200; reg_z = valve_z; sv_sep = sprinkler_valve_dims()[2]; // Variation 1: with pressure regulator. // I didn't end up using this variation. module plan1() { pipe(length=reg_z-50) connector() pipe(length=50) rotate(180) elbow(type1=SLIP, type2=MIPT, sched=80) rotate(180) regulator() rotate(180) elbow(type1=MIPT, type2=SLIP) pipe(length=50) rotate(180) tee(size3=S34, type3=FIPT) { pipe(length=manifold_depth + reg_z - 80) rotate(-90) elbow() pipe(length=sv_sep) tee() { pipe(length=sv_sep) elbow() pipe(length=valve_z + manifold_depth) rotate(-90) sprinkler_valve_assy() pipe(length=valve_z); pipe(length=valve_z + manifold_depth) rotate(-90) sprinkler_valve_assy() pipe(length=valve_z); } rotate(-90) hosebib(); } } // Variation 2: Starting downstream of existing regulator, // at a different point in the system. // This is what I ended up building. module plan2() { rotate([180,0,0]) connector(type1=MIPT, size=S1) pipe(length=50, size=S1) rotate(90) elbow(size=S1) pipe(length=100, size=S1) corner(size1=S1,size2=S1) { pipe(length=400, size=S1) rotate(180) sprinkler_valve_assy(size=S1); pipe(length=sv_sep) tee() { pipe(length=sv_sep) elbow() pipe(length=300) rotate(180) tee(type3=FIPT) { pipe(length=70) rotate(-90) sprinkler_valve_assy() pipe(length=400) rotate(-90) elbow() pipe(length=1450) rotate(90) angle45() pipe(length=1000); rotate(90) hosebib(); } pipe(length=400) rotate(90) sprinkler_valve_assy() pipe(length=430) rotate(-90) elbow() pipe(length=1500) rotate(90) angle45() pipe(length=1000); } }; } plan2(); // A sprinkler valve assembly, with the two PVC connectors attached. // Note that this is a connector; the input side connects to the // previous component and the child is the output side. module sprinkler_valve_assy(size=S34) { connector(type1=SLIP, type2=MIPT, size=size) sprinkler_valve(size=size) connector(type1=MIPT, type2=SLIP, size=size) children(); }