Plumbing!

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Plumbing!

JordanBrown
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 [hidden email]

// 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();
}



_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org