// PipeMaker by Lex Neva // // Pipemaker helps you easily create continuous lengths of pipe using cylinders and torus segments. The simple dialog-driven // interface always lines pieces up perfectly to give you one long continuous pipe. It's useful for creating industrial // complicated pipe messes, weird curlicues, and, of course, for writing your name in prims in the air. // // This script is released under the GNU Public License (http://www.gnu.org for a copy). In short, this means you may copy, // redistribute, and modify this script to your heart's content, so long as my name stays in it and your new version is also // freely redistributable. For more details, read the full license. If you really must provide me with some kind of compensation // for my work, feel free to heap praises upon me in the forums, rate me, and/or send me lindens. You can also check out my // store in Eldora. // // Okay, now that's out of the way. Here's how to use this thing. // // If you've managed to pick up a copy of this script in the wild, you'll have to build the object. Fortunately, that's easy. // // 1. Build a default cylinder, name it PipeMaker (the name's important) // 2. Place this script in the PipeMaker cylinder. // 3. Take a copy. (leave one rezzed) // 4. Find the copy in your inventory, and place it in the rezzed one. // 5. Take the whole mess into your inventory. // 6. Rez a copy and get started! // // The above process is necessary because the pipemaker uses a safe form of self-replication. // // Now, how to actually make pipes. // // You should be able to just rez the PipeMaker object and go to it. A dialog will pop up, and a new pipe piece will stick itself // onto the end of the first one. This new piece (the red one) is the one you're currently working on. Poke a few buttons. // Commands like "length+" will make the length of a straight piece longer. The more pluses, the longer it'll get. Minuses will // shrink it instead. // // Click the "curve" button if you want to turn a corner instead. The radius commands will change how sharp the curve is: bigger // radius means a more gradual curve. The arc commands will determine how much of a circle the curved piece uses. 90 means to // turn a right angle, 180 means to do a "U" bend, etc. 360 probably won't be useful, but the option IS there. Tilt will // make the curved piece rotate up or down. Just play with it, you'll get it. // // There are only so many buttons allowable in a script dialog, so I wasn't able to include all of the commands I wanted to. You // can still do things like "radius+++" if the radius+ button isn't going fast enough: just type /1 radius+++. You can use up to // three pluses or minuses. // // When you're done, just delete the extra red piece. I didn't have enough room for a "finish" button. // // If you want to have a hollow pipe, rez the first PipeMaker, and change its hollow value. Tweak the length so that the red // pipe updates. From then on, the hollow will carry through. You could, if you want, change the hollow at any time, and // the setting will carry on to pieces after that one. If you want to change the thickness of the pipe, just change the // X and Y size of the starting cylinder. I recommend that the X and Y settings should be the same. I was too lazy to make // it work with oval pipes. You can change the size any time, but I don't recommend messing with curved pieces, because getting // everything to match up nicely is difficult. // // The rest is for advanced users. You can skip this stuff. // // Why isn't there finer control for arc and tilt? Why does radius change in such weird increments? // // The simple answer is that the object properties this script uses, such as hole size and cut, only allow two decimal places of // precision. If I let you use any arc value, SL will "round" to the nearest 18 or so degrees, and that'll mean the next pipe // doesn't join on perfectly. Radius is even more complicated. You can set the radius of a torus to a fairly fine degree of // precision, but the real limiting factor is the hole size. If the hole size that would be needed for a certain radius // of curve doesn't line up on an even 0.01, the pipe's cross-section won't match up. When you use the radius+ and radius- // buttons, I actually decrement and increment the hole size, and figure out the torus's radius from that, which guarantees that // everything lines up beautifully. Incidentally, I also change the tilt value in increments of 15 degrees, because that way // the vertices of the cylinders and tori always line up nicely. // // That said, you can override these limits manually if you really want to. I figure if you want to do this, you probably have // a good reason. Just utter these commands on channel 1 (by prefixing them with "/1"): // // radius 2.5 // tilt 35.0 // arc 90.0 // length 5.0 // radius+++ // (any other command on the buttons) // reset (use only if the red prim fails to rez or is accidentally deleted) // // Just be careful, especially with radius. If you don't watch it, you can find that things aren't lining up nicely... it's just // due to the limits of primitive properties. The dialog optiosn should give you a fair degree of freedom. // float torus_radius; float torus_rot; float torus_arc; float cylinder_length; integer type; integer channel; integer listenHandle; vector offsetRot(vector initial_position, vector center_position, rotation rot_amount) { vector offset = initial_position - center_position; vector final_position; //The following line calculates the new coordinates based on //the rotation & offset final_position = offset * rot_amount; //Since the rotation is calculated in terms of our offset, we need to add //our original center_position back in - to get the final coordinates. final_position += center_position; return final_position; } makeTorus() { //llSay(0,"Making torus part with radius " + (string)torus_radius + " rotation " + (string)torus_rot + " arc " + (string)torus_arc); vector myScale = llGetScale(); rotation torus_rotation; vector torus_position; vector torus_scale; vector torus_cut; vector torus_hole_size; list params = llGetPrimitiveParams([PRIM_TYPE]); float hollow = llList2Float(params,3); if (llList2Integer(params,0) == PRIM_TYPE_CYLINDER) { torus_rotation = llGetRot(); torus_position = llGetPos() + llRot2Up(torus_rotation) * myScale.z * 0.5 - llRot2Left(torus_rotation) * (torus_radius - myScale.x/2.0); vector face_center = llGetPos() + llRot2Up(torus_rotation) * myScale.z * 0.5; rotation rot = llAxisAngle2Rot(llRot2Up(torus_rotation), DEG_TO_RAD * torus_rot); torus_position = offsetRot(torus_position, face_center, rot); torus_rotation = torus_rotation * rot; torus_scale =; torus_hole_size = <1 .0,myScale.y/(2*torus_radius),0.0>; } else { vector cut = llList2Vector(params,2); vector hole_size = llList2Vector(params,5); float d = hole_size.y * myScale.y; torus_rotation = llGetRot(); torus_position = llGetPos(); torus_rotation *= llAxisAngle2Rot(llRot2Fwd(torus_rotation),cut.y*TWO_PI); torus_position += llRot2Left(torus_rotation) * (myScale.z/2.0 - torus_radius); vector face_center = torus_position + llRot2Left(torus_rotation) * (torus_radius - d/2.0); rotation rot = llAxisAngle2Rot(llRot2Up(torus_rotation), DEG_TO_RAD * torus_rot); torus_position = offsetRot(torus_position, face_center, rot); torus_rotation = torus_rotation * rot; torus_scale = ; torus_hole_size = <1 .0,d/(2*torus_radius),0.0>; } torus_cut = <0.0, torus_arc/360.0, 0.0>; llSay(channel,"TORUS," + (string)torus_position + "," + (string)torus_rotation + "," + (string)torus_scale + "," + (string)torus_cut + "," + (string)hollow + "," + (string)torus_hole_size); } makeCylinder() { //llSay(0,"Making cylinder with length " + (string)cylinder_length); vector myScale = llGetScale(); rotation cylinder_rotation; vector cylinder_position; vector cylinder_scale; float d; list params = llGetPrimitiveParams([PRIM_TYPE]); float hollow = llList2Float(params,3); if (llList2Integer(params,0) == PRIM_TYPE_TORUS) { vector cut = llList2Vector(params,2); vector hole_size = llList2Vector(params,5); d = hole_size.y * myScale.y; cylinder_rotation = llGetRot(); rotation rot = llAxisAngle2Rot(llRot2Fwd(cylinder_rotation),cut.y *TWO_PI); cylinder_rotation *= rot; cylinder_position = llRot2Left(llGetRot()) * ((myScale.y - d)/2.0); cylinder_position *= rot; cylinder_position += llGetPos(); cylinder_position += llRot2Up(cylinder_rotation) * (cylinder_length/2.0); } else { d = myScale.y; cylinder_position = llGetPos(); cylinder_rotation = llGetRot(); cylinder_position += llRot2Up(cylinder_rotation) * ((myScale.z + cylinder_length)/2.0); } cylinder_scale = ; llSay(channel,"CYLINDER," + (string)cylinder_position + "," + (string)cylinder_rotation + "," + (string)cylinder_scale + "," + (string)hollow); } update() { if (type == PRIM_TYPE_CYLINDER) makeCylinder(); else makeTorus(); } rezNext() { channel = 10000 + llFloor(llFrand(1000000)); //llOwnerSay("Channel = " + (string)channel); llRezObject("PipeMaker",llGetPos(), ZERO_VECTOR, ZERO_ROTATION, channel); llSleep(1.0); } sendDialog() { if (type == PRIM_TYPE_CYLINDER) { llDialog(llGetOwner(),"Straight piece\n\nlength = " + (string)cylinder_length + " meters",["length+","length++","length+++","length-","length--","length---","curve","next"],1); } else { llDialog(llGetOwner(),"Curve piece\n\nradius = " + (string)torus_radius + " meters\narc = " + (string)torus_arc + " degrees\ntilt = " + (string)torus_rot + " degrees",["tilt+","tilt++","next","tilt-","tilt--","straight","radius+","arc+","arc++","radius-","arc-","arc--"],1); } } float getDiameter() { vector scale = llGetScale(); return scale.x; } init() { float d = getDiameter(); if (d > 2.5) { torus_radius = d; } else { torus_radius = d*2.0; } torus_rot = 0.0; torus_arc = 90.0; cylinder_length = 1.0; type=PRIM_TYPE_CYLINDER; } default { on_rez(integer param) { if (param) { llListen(param,"","",""); llSetColor(<1 .0,0.0,0.0>,ALL_SIDES); } else { llSetColor(1><1 ,1,1>,ALL_SIDES); state configure; } } listen(integer c, string name, key id, string message) { list params = llCSV2List(message); list primParams; if (llList2String(params,0) == "TORUS") { primParams = [PRIM_POSITION, (vector)llList2String(params,1), PRIM_ROTATION, (rotation)llList2String(params,2), PRIM_SIZE, (vector)llList2String(params,3), PRIM_TYPE, PRIM_TYPE_TORUS, // type PRIM_HOLE_DEFAULT, // hole type (vector)llList2String(params,4), // cut (float)llList2String(params,5), // hollow <0.0,0.0,0.0>, // twist (vector)llList2String(params,6), // hole size 0><0.0,0.0,0.0>, // top shear 0><0.0,1.0,0.0>, // advanced cut 0><0.0,0.0,0.0>, // taper 1.0, // revolutions 0.0, // radius offset 0.0 // skew ]; } else if (llList2String(params,0) == "CYLINDER") { primParams = [PRIM_POSITION, (vector)llList2String(params,1), PRIM_ROTATION, (rotation)llList2String(params,2), PRIM_SIZE, (vector)llList2String(params,3), PRIM_TYPE, PRIM_TYPE_CYLINDER, // type PRIM_HOLE_DEFAULT, // hole type 0><0.0,1.0,0.0>, // cut (float)llList2String(params,4), // hollow 0><0.0,0.0,0.0>, // twist <1 .0, 1.0, 0.0>, // top size <0.0,0.0,0.0> // top shear ]; } else if (llList2String(params,0) == "DONE") { llSetColor(<1 .0,1.0,1.0>,ALL_SIDES); state configure; } llSetPrimitiveParams(primParams); } } state configure { state_entry() { listenHandle = llListen(1,"",llGetOwner(),""); init(); sendDialog(); rezNext(); update(); } on_rez(integer param) { llListenRemove(listenHandle); listenHandle = llListen(1,"",llGetOwner(),""); llSetColor(1><1 ,1,1>,ALL_SIDES); init(); sendDialog(); rezNext(); update(); } changed(integer change) { if (change & CHANGED_SHAPE) { // hollow update(); } else if (change & CHANGED_SCALE) { float d = getDiameter(); // must make sure the hole size will be nice and prim size will be under 10.0 if (d > 2.5) torus_radius = d; else torus_radius = d * 2.0; update(); } } moving_end() { update(); } listen(integer c, string name, key id, string message) { list parts = llParseString2List(message,[" "],[]); string command = llList2String(parts,0); // start with chat-only commands, don't resend dialog (it gets spammy) if (command == "radius") { torus_radius = (float)llList2String(parts,1); } else if (command == "rot" || command == "tilt") { torus_rot = (float)llList2String(parts,1); } else if (command == "arc") { torus_arc = (float)llList2String(parts,1); } else if (command == "length") { cylinder_length = (float)llList2String(parts,1); } else if (command == "done" || command == "next") { llSay(channel,"DONE"); // now commit suicide llRemoveInventory(llGetScriptName()); state default; // and just in case, stop doing anything until the script's removed. } else { // dialog-driven commands follow, this big else block is so I can send a dialog only for dialog commands if (command == "curve") { type = PRIM_TYPE_TORUS; } else if (command == "straight") { type = PRIM_TYPE_CYLINDER; } else if (llGetSubString(command,0,5) == "length") { integer strength = llStringLength(command) - 6; float change = llList2Float([0.1,0.5,1.0],strength - 1); if (llGetSubString(command,6,6) == "-") change *= -1; cylinder_length += change; if (cylinder_length <0.01) cylinder_length = 0.01; if (cylinder_length > 10.0) cylinder_length = 10.0; } else if (llGetSubString(command,0,5) == "radius") { integer strength = llStringLength(command) - 6; float change = llList2Float([0.01,0.05,0.1], strength - 1); if (llGetSubString(command,6,6) == "+") change *= -1; float d = getDiameter(); float hole_size = d/(torus_radius * 2.0); hole_size += change; if (hole_size ><0.05) { //llOwnerSay("clamped to 0.05"); hole_size = 0.05; } if (hole_size > 0.5) { //llOwnerSay("clamped to 0.5"); hole_size = 0.5; } float new_radius = d/(hole_size * 2.0); if (new_radius ><= 5.0) { torus_radius = new_radius; } } else if (llGetSubString(command,0,2) == "arc") { integer strength = llStringLength(command) - 3; float change = llList2Float([0.05,0.1,0.2], strength - 1); if (llGetSubString(command,3,3) == "-") change *= -1; float cut = torus_arc / 360.0; cut += change; if (cut > 1.0) cut = 1.0; if (cut ><0.05) cut = 0.05; torus_arc = cut * 360.0; } else if (llGetSubString(command,0,3) == "tilt") { integer strength = llStringLength(command) - 4; float change = llList2Float([15.0,45.0,90.0], strength - 1); if (llGetSubString(command,4,4) == "-") change *= -1; torus_rot += change; // boundaries wrap } else if (command == "reset") { init(); rezNext(); } else { return; // invalid command } sendDialog(); } // in any case, if we get here, it's time to update the next segment's properties update(); } touch_start(integer num) { sendDialog(); } object_rez(key id) { llGiveInventory(id, "PipeMaker"); } }