Terminal Takeaway πŸ₯‘

Script for terminal themeing

Highlighting a brilliant project by lemnos which lets you change your terminal’s theme on the fly from a selection of 274 themes.

Install

Option 1. I want this installed for use by my user account

# Download to user's custom executable location
mkdir -p ~/.local/bin/
curl -o ~/.local/bin/theme.sh 'https://raw.githubusercontent.com/lemnos/theme.sh/master/theme.sh'

# Confirm no shenanigans
nano ~/.local/bin/theme.sh

# Make executable to current user
chmod u+x ~/.local/bin/theme.sh

Option 2. I want this installed for use by every account

Note: I changed the installation destination from /usr/bin to /usr/local/bin to follow filesystem standards.

# Download to system's custom executable location
sudo curl -o /usr/local/bin/theme.sh 'https://raw.githubusercontent.com/lemnos/theme.sh/master/theme.sh'

# Confirm no shenanigans
nano /usr/local/bin/theme.sh

# Make executable to all users
sudo chmod +x /usr/local/bin/theme.sh

Try it out

# Output CLI instructions
theme.sh

Pick a theme interactively:

theme.sh -i # Enter interactive mode to pick a theme:
# Use: theme.sh -i2 for terminals limited to 16 colors.

# Use the arrow keys or type to search
# Enter to select a theme
# Ctrl + C to quit

Before:

After:

Add this command to your .bashrc to make a theme permanent:

nano ~/.bashrc
# Add the following replacing "tempus-autumn" w/ your desired theme:
theme.sh tempus-autumn

# Save & quit
# Open a new terminal
1 Like

Learned about vertical tabs today…

image

3 Likes

We all love ls but what if you want a tree like view, just type:

ls -R | grep ":$" | sed -e 's/:$//' -e 's/[^-][^\/]*\//--/g' -e 's/^/ /' -e 's/-/|/'

Enjoy your output.

1 Like

Nice use of the cascading sed substitutions!

1 Like

Maze Game

I created a timed maze game that reads a player defined map using arbitrary text. Has hit detection and a timed win condition.

Add an X for the player, an E for the exit, spaces are where the player can travel and anything else blocks movement.

I’ve been working with arrays so much the idea popped into my head so I put it into code. You can copy/paste in any text based map you find on the Internet (so long as traversable areas are spaces) and the script will make it work. Just add an X for the player and E for exit.

#!/usr/bin/env bash
#
# === USER DEFINED MAP ===
#
# Map key:
#   - X for the player
#   - E for exit
#   - Space for traversable
#   - Any other character blocks the player's movement

{ MAP_DATA=$(</dev/stdin); } <<\EOF

   Movement: AWSD or HJKL     
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ/E\β–ˆ 
β–ˆ       β–ˆ           β–ˆ       β–ˆ 
β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ 
β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ 
β–ˆ   β–ˆ   β–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β–ˆ   β–ˆ   β–ˆ 
β–ˆ   β–ˆ           β–ˆ       β–ˆ   β–ˆ 
β–ˆ   β–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β–ˆ 
β–ˆ   β–ˆ   β–ˆ       β–ˆ   β–ˆ       β–ˆ 
β–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 
β–ˆ           β–ˆ           β–ˆ   β–ˆ 
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β–ˆ   β–ˆ 
β–ˆ       β–ˆ       β–ˆ   β–ˆ   β–ˆ   β–ˆ 
β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β–ˆ 
β–ˆ X β–ˆ       β–ˆ               β–ˆ 
β–ˆ ^ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 
                              
EOF

# === DEFINE FUNCTIONS ===

MOVE_PLAYER(){
	# Define where the player is attempting to move
	ATTEMPT_X=$(( $1 + $PLAYER_X ))
	ATTEMPT_Y=$(( $2 + $PLAYER_Y ))
	ATTEMPT_MAP_Y_LINE=${MAP_Y[$ATTEMPT_Y]}

	# Prevent out of bounds travel
	if [ "$ATTEMPT_X" = "-1" -o "$ATTEMPT_Y" = "-1" ]; then return 1; fi

	# Is the player moving onto a win condition?
	if [ "${ATTEMPT_MAP_Y_LINE:$ATTEMPT_X:1}" = "E" ]; then

		# Get the finish time, print victory statement and exit
		FINISH_TIME=$(( SECONDS - GAME_START ))
		printf "You escaped in %s seconds" $FINISH_TIME
		if [ $PLAYER_Y == 0 ]; then printf " using \e[0;91mrule 0\e[0m"; fi
		printf "!\n\n"
		exit 1

	fi

	# Is the player attempting to move into an empty space?
	if [ "${ATTEMPT_MAP_Y_LINE:$ATTEMPT_X:1}" = $EMPTY_BLOCK ]; then

		# Erase current player location
		PLAYER_MAP_Y_LINE=${MAP_Y[$PLAYER_Y]}
		MAP_Y[$PLAYER_Y]="${PLAYER_MAP_Y_LINE:0:$PLAYER_X}${EMPTY_BLOCK}${PLAYER_MAP_Y_LINE:$(($PLAYER_X+1))}"

		# Move player coordinates
		PLAYER_X=$ATTEMPT_X
		PLAYER_Y=$ATTEMPT_Y

		# Draw new player location on updated and/or different PLAYER_MAP_Y_LINE
		PLAYER_MAP_Y_LINE=${MAP_Y[$PLAYER_Y]}
		MAP_Y[$PLAYER_Y]="${PLAYER_MAP_Y_LINE:0:$PLAYER_X}X${PLAYER_MAP_Y_LINE:$(($PLAYER_X+1))}"

		# Print map
		PRINT_MAP

	fi
}

PRINT_MAP(){
	clear
	#printf '\e[0;92m' # Make green
	printf '%s\n' "${MAP_Y[@]}"
	#printf '\e[0;0m' # Reset color
}

USER_INPUT_LOOP(){
	# Wait for the user to press a key and define it as $KEY
	read -s -n 1 KEY

	# Move the player according to the KEY's associated movement value in KEY_TO_MOVEMENT
	MOVE_PLAYER ${KEY_TO_MOVEMENT[$KEY]}

	USER_INPUT_LOOP
}

# === STARTUP ===

# Declare human input keys with movement modifiers
declare -A KEY_TO_MOVEMENT=( [w]="0 -1" [d]="1 0" [a]="-1 0" [s]="0 1" [k]="0 -1" [l]="1 0" [h]="-1 0" [j]="0 1" )

EMPTY_BLOCK="." # Special ANSI invisible character
IFS_OLD="$IFS";
IFS=""

# Replace all spaces with the EMPTY_BLOCK to simplify sequencing and add map to MAP_Y array.
MAP_DATA=$(echo $MAP_DATA | sed "s/ /$EMPTY_BLOCK/g")
readarray -t MAP_Y <<< "$MAP_DATA"

# Get player location by counting prior new lines and prior characters on the player's MAP_Y line.
PLAYER_Y=$(( `echo ${MAP_DATA%X*} | wc -l` - 1 ))
PLAYER_X=$(( `echo ${MAP_Y[$PLAYER_Y]%X*} | wc -m` -1 ))

IFS="$IFS_OLD"
GAME_START=$SECONDS
PRINT_MAP
USER_INPUT_LOOP

ncat: Hosting a public website with a single command

sudo dnf install nmap-ncat # Fedora/CentOS/RHEL
sudo apt install ncat # Debian/Ubuntu
ncat -klp 8080 --send-only -c 'printf "HTTP/1.1 200 OK\n\n%s" "<html><body><h1>Terminal Tuesday !!</h1></body></html>";'
# (Ctrl-C to quit)

# Options in use:
# -k, --keep-open            Accept multiple connections in listen mode
# -l, --listen               Bind and listen for incoming connections
# -p, --source-port port     Specify source port to use
#     --send-only            Only send data, ignoring received; quit on EOF
# -c, --sh-exec <command>    Executes the given command via /bin/sh

# Test in another terminal or Web browser:
curl http://127.0.0.1:8080
curl http://YOUR_LAN_OR_PUBLIC_IP:8080

Other Examples:

Similar to rm -rf these scripts are a great way to get yourself in trouble. Close off port 8080 on your firewall before playing, think before you type, know that any website can send you to a local address like 127.0.0.1:8080 in a hidden iframe and consider security is hard even for sysadmins.

# Introducing option: --allow <host>
# Allow only given hosts to connect to Ncat

# Return all environment variables accessible to the process (yikes)
ncat -klp 8080 --allow 127.0.0.1 --send-only -c 'printf "HTTP/1.1 200 OK\n\n"; printenv;'

# Run a local script and return the output (yikes)
ncat -klp 8080 --allow 127.0.0.1 --send-only -c 'printf "HTTP/1.1 200 OK\n\n"; ./run.sh;'

# Return an HTML page
ncat -klp 8080 --send-only -c 'printf "HTTP/1.1 200 OK\n\n"; cat ./index.html;'

# IP Lookup Server:
ncat -klp 8080 --send-only -c 'printf "HTTP/1.1 200 OK\n\n%s" "$NCAT_REMOTE_ADDR";'
# More ncat env variables: https://secwiki.org/w/Ncat/Environment_variables

Bash can look very sexy sometimes:

for n in {1..5}; do echo $n; done

And the output:

1
2
3
4
5
1 Like

Something I learned recently…

If any amount of leading zeros are added, the output will always be equal to the maximum number of possible digits.

for n in {01..10000}; do echo $n; done

00001
00002
…
09999
10000

for n in {1..010000}; do echo $n; done

000001
000002
…
009999
010000

2 Likes

This might be the coolest usage of an old terminal with a modern Linux machine I have yet seen. This is purely entertainment but something I have watched more than once, purely for the smiles.

This is part 2 where it is actually being employed.

For what it is worth, he is using Kubuntu for his Linux distribution.

Because it isn’t nice to just post a part 2, here is the Part 1

6 Likes

If this worked with Fusion 360… (insert dream sequence here)…

1 Like

If Fusion360 was fully supported on Linux by Autodesk… (insert dream sequence here)…
:slight_smile:

That video made me want to check out openSCAD, any time I can do something from the terminal I’m all in. So far it is really neat. Thanks for sharing!

2 Likes

I think I need to try to understand openSCAD too. It’s on the list… but not high on the list.

I use openSCAD – it just requires you to break an object down into shapes that produce the whole. I think one of the best features of it is that it’s parametric. You can search scad files on thingiverse – I have discovered some mind-blowing mathematical functions – which reminded me that there are people in the world who have forgotten more math than I have ever known.

what does a simple part, like a screw, look like? or a keyboard cap for example?

push_rivet

I’ll first show you the simplicity of the code with this very simple design of a push pin for the plastic inner fender for a car. The parametric nature of openSCAD allows me to return to the code and change a variable easily and with great precision.

//  Push Rivet for Car
//  Mark Cain
//  August 22, 2020
//  Revision 0.3 //
//  Revision 0.2 // measurements in mm height=16, cone_base=14, cone_tip=6
//  Revision 0.1 // measurements in mm height=20, cone_base=15, cone_tip=5

cone_base = 14;
cone_height = 16;
cone_tip = 6;

ring_elevation = 6;
ring_height = 4;

connector_height =10;

      rotate([0, 0 , 0]){

          difference() {

              union() { // beginning of the visible union

                  cylinder(h=cone_height, d1=cone_base, d2=cone_tip);


              };  // end of the visible union


              union() { // beginning of the cut union

                  translate([0, 0, ring_elevation])
                    cylinder(h=ring_height, d=cone_base);



              };  // end of the cut union
          }; // end of project difference

          // inside column for the ring's center

          cylinder(h=connector_height, d=cone_tip+2);
      };  // end of rotate

// variables
$fn=360;
2 Likes

Here is a more complex part – an auger that I incorporated into a design I’m making. For complex models like this, I rely on finding these functions online.

// Parametric Printable Auger
// It is licensed under the Creative Commons - GNU GPL license.
// 2013 by William Gibson
// http://www.thingiverse.com/thing:96462

use <utils/build_plate.scad>



////////////
//Examples//
////////////

//Simple Example
// auger(r1=1/8*inch, r2=.75*inch, h=1*inch,
// turns=2, multiStart=1, flightThickness = 0.6,
// overhangAngle=20, supportThickness=0.0);

//Multistart example
// auger(r1=1/2*inch, r2=2*inch, h=2*inch,
// turns=1, multiStart=3, flightThickness = 0.6,
// overhangAngle=20, supportThickness=0.0);

//Support
// auger(r1=1/2*inch, r2=2*inch, h=2*inch,
// turns=2, multiStart=1, flightThickness = 0.6,
// overhangAngle=10, supportThickness=0.4);


//////////////////////
//CUSTOMIZER OPTIONS//
//////////////////////

/* [Auger] */

//The total amount of twist, in degrees
Auger_twist = 877; //[90:1080]

//The radius of the auger's "flight" past the shaft
Auger_flight_radius = 20; //[5:50]

//The number of "flights"
Auger_num_flights = 2; //[1:5]

//The height, from top to bottom of the "shaft"
Auger_flight_length = 100; //[10:200]

/* [Printer] */

//The overhang angle your printer is capable of
Printer_overhang_capability = 20; //[0:40]

//The thickness of perimeter support material
Auger_perimeter_thickness = 0.0; //[0:None, 0.8:Thin, 2:Thick]

/* [Uninteresting] */

//The radius of the auger's "shaft"
Auger_shaft_radius = 10; //[1:25]

//The thickness of the "flight" (in the direction of height)
Auger_flight_thickness = 1;  //[0.2:Thin, 1:Medium, 10:Thick]

Auger_handedness = "right";  //["right":Right, "left":Left]

/* [Build plate] */

//for display only, doesn't contribute to final object
build_plate_selector = 3; //[0:Replicator 2,1: Replicator,2:Thingomatic,3:Manual]
//when Build Plate Selector is set to "manual" this controls the build plate x dimension
build_plate_manual_x = 200; //[100:400]
//when Build Plate Selector is set to "manual" this controls the build plate y dimension
build_plate_manual_y = 200; //[100:400]

build_plate(build_plate_selector,build_plate_manual_x,build_plate_manual_y);

/* [Hidden] */

M_PI = 3.14159;
mm = 1;
inch = 25.4 * mm;


auger(
r1 = Auger_shaft_radius,
r2 = Auger_shaft_radius + Auger_flight_radius,
h = Auger_flight_length,
overhangAngle = Printer_overhang_capability,
multiStart = Auger_num_flights,
flightThickness = Auger_flight_thickness,
turns = Auger_twist/360,
pitch=0,
supportThickness = Auger_perimeter_thickness,
handedness=Auger_handedness,
//$fn=50,
$fa=12,
$fs=5
);



//////////////////////
//Auger Library Code//
//////////////////////

//Notes:
//Specify 'pitch' OR 'turns' (pitch overrides turns)
//r1 >= 1mm please
//flightThickness >= extrusion thickness of your printer
//supportThickness >= 2 * extrusion width of your printer, or zero to turn off.

module auger(r1 = 0.5*inch, r2 = 0.75*inch, h=1*inch, multiStart=1,
turns=1, pitch=0,
flightThickness = 0.2*mm, overhangAngle=20, supportThickness=0*mm,
handedness="right" /*"left"*/)
{
	assign(_turns = ((pitch>0?h/(pitch+flightThickness):turns)))
	{
		if(pitch != 0)
		{
			echo("Pitch defined - ignoring turns parameter");
			//Each 1 turn is a height of (pitch+flightThickness)
			//A height of h will make x turns where x = h / (pitch+flightThickness)

			echo("Calculated turns = ", _turns);
		}
		else
		{
			if(turns < 0)
			{
				echo("ERROR: Cannot handle negative turns. Use handedness='left' instead to reverse rotation.");
			}
		}


		assign(extraFlight = tan(overhangAngle)*(r2-r1))
		{
			difference()
			{
				auger_not_truncated(r1=r1, r2=r2, h=h, turns=_turns,
				flightThickness=flightThickness, overhangAngle=overhangAngle,
				multiStart=multiStart, supportThickness=supportThickness,
				handedness=handedness=="right"?1:-1);

				//Cut off bottom of auger so it's printable.
				translate([0,0,-extraFlight])
				cube([r2 * 3,r2 * 3,2*extraFlight], center=true);
			}
		}
	}

}



module auger_not_truncated(r1 = 0.5*inch, r2 = 0.75*inch, h=1*inch, turns=1, flightThickness = 0.2*mm, overhangAngle=20, multiStart=1, supportThickness=0*mm, handedness=1)
{
	assign(extraFlight = tan(overhangAngle)*(r2-r1))
	{
		if(supportThickness > 0)
		{
			difference()
			{
				cylinder(h=h, r=r2+0.1, $fs=0.5);

				translate([0,0,-1])
				cylinder(h=h+2, r=r2-supportThickness+0.1, $fs=0.5);
			}
		}

		cylinder(r=r1, h=h,$fs=0.5); //Central shaft


		for(start=[1:1:multiStart]) //render each flight
		{
			rotate([0,0,handedness*360*(start-1)/multiStart])
			augerFlight(flightThickness=flightThickness, turns=turns, rHidden=(r1>6?r1-5:1), r1=r1, r2=r2, h=h, extraFlight=extraFlight, handedness=handedness);

		}
	}
}

module augerFlight(flightThickness, turns, rHidden, r1, r2, h, extraFlight, handedness)
{
	if($fs < 0.1)
	{
		echo("WARNING: $fs too small - clamping to 0.1");
	}
	if($fa < 0.1)
	{
		echo("WARNING: $fa too small - clamping to 0.1");
	}

	//Calculate numSteps based on $fn, $fs, $fa
	assign($fs = max(0.1, $fs), $fa = max(0.1, $fa),
	numSteps=($fn > 0.0) ? $fn :
	max(5,
	max(h/(max($fs,0.1)),
	max(360.0 * turns / $fa,
	r2*2*M_PI*turns / max($fs,0.1)))))
	{
		echo("Number of Steps calculations:");
		echo("minimum",5);
		echo("height step", h/(max($fs,0.1)));
		echo("angle", 360.0 * turns / $fa);
		echo("perimeter size", r2*2*M_PI*turns / max($fs,0.1));
		echo("numSteps = maximum: ", numSteps);

		assign(heightStep=((h-(flightThickness))/numSteps))
		{
			translate([0,0,-extraFlight]) //Move down so the extraFlight material is below z=0
			{

				for(step=[0:1:numSteps-1]) //For each step in a flight
				{
					rotate([0,0,handedness*turns*step/numSteps*360])
					translate([0,0,heightStep*step])
					if(handedness==1)
						augerPolyhedron(flightThickness=flightThickness, extraFlight=extraFlight, rHidden=rHidden, r1=r1, r2=r2, turns=turns, numSteps=numSteps, heightStep=heightStep);
					else
						mirror([1,0,0])
						augerPolyhedron(flightThickness=flightThickness, extraFlight=extraFlight, rHidden=rHidden, r1=r1, r2=r2, turns=turns, numSteps=numSteps, heightStep=heightStep);

				}
			}
		}
	}

	module augerPolyhedron(flightThickness, extraFlight, rHidden, r1, r2, turns, numSteps, heightStep)
	{
		//_1 is first angle, _2 is second angle
		//_I is inside, _O is outside

		assign(top_1_I=flightThickness+extraFlight, bot_1_I=0,
		top_1_O=flightThickness+extraFlight, bot_1_O=extraFlight,
		degOverlap=0.1,
		rHiddenCorrection=(r1-rHidden)/(r2-r1)
		)
		{
			//echo(rHidden, r1, r2);
			//echo("rHiddenCorrection=",rHiddenCorrection);
			//echo("rHiddenCorrection*extraFlight=",rHiddenCorrection*extraFlight);
			//echo("heightStep=",heightStep);

			polyhedron(
			points=[
			[0,rHidden,bot_1_I-rHiddenCorrection*extraFlight],	//0
			[0,rHidden,top_1_I],					//1
			[0,r2, bot_1_O], 						//2
			[0,r2, top_1_O], 						//3

			[-rHidden*sin(360*turns/numSteps+degOverlap), //4
			rHidden*cos(360*turns/numSteps+degOverlap),
			bot_1_I+heightStep-rHiddenCorrection*extraFlight], //+rHiddenCorrection*heightStep-rHiddenCorrection*extraFlight],
			//

			[-rHidden*sin(360*turns/numSteps+degOverlap), //5
			rHidden*cos(360*turns/numSteps+degOverlap),
			top_1_I+heightStep],

			[-r2*sin(360*turns/numSteps+degOverlap), //6
			r2*cos(360*turns/numSteps+degOverlap),
			bot_1_O+heightStep],

			[-r2*sin(360*turns/numSteps+degOverlap), //7
			r2*cos(360*turns/numSteps+degOverlap),
			top_1_O+heightStep]

			],
			triangles=[
			[0,1,2], //"triangle" 1
			[2,1,3],

			[4,6,5], //"triangle" 2
			[6,7,5],

			[1,4,5],
			[1,0,4], //Inner "square"

			[3,7,6],
			[3,6,2], //Outer "square"

			[0,2,4],
			[4,2,6], //Bottom "square"

			[1,5,3],
			[5,7,3], //Top "square"

			]);
		}
	}

	module augerPolyhedronBackup(flightThickness, extraFlight, r1, r2, turns, numSteps, heightStep)
	{
		//_1 is first angle, _2 is second angle
		//_I is inside, _O is outside
		assign(top_1_I=flightThickness+extraFlight, bot_1_I=0, top_1_O=flightThickness+extraFlight, bot_1_O=extraFlight, degOverlap=0.1)
		{
			polyhedron(
			points=[
			[0,r1,bot_1_I],		//0
			[0,r1,top_1_I],		//1
			[0,r2, bot_1_O], //2
			[0,r2, top_1_O], //3

			[-r1*sin(360*turns/numSteps+degOverlap), //4
			r1*cos(360*turns/numSteps+degOverlap),
			bot_1_I+heightStep],

			[-r1*sin(360*turns/numSteps+degOverlap), //5
			r1*cos(360*turns/numSteps+degOverlap),
			top_1_I+heightStep],

			[-r2*sin(360*turns/numSteps+degOverlap), //6
			r2*cos(360*turns/numSteps+degOverlap),
			bot_1_O+heightStep],

			[-r2*sin(360*turns/numSteps+degOverlap), //7
			r2*cos(360*turns/numSteps+degOverlap),
			top_1_O+heightStep]

			],
			triangles=[
			[0,1,2], //"triangle" 1
			[2,1,3],

			[4,6,5], //"triangle" 2
			[6,7,5],

			[1,4,5],
			[1,0,4], //Inner "square"

			[3,7,6],
			[3,6,2], //Outer "square"

			[0,2,4],
			[4,2,6], //Bottom "square"

			[1,5,3],
			[5,7,3], //Top "square"


			]);
		}
	}
}
2 Likes

Clean old configuration files from your Debian based system:

apt purge $(dpkg -l | awk '/^rc/ { print $2 }')

It is very useful after doing an in-place release upgrade. Though be careful and read the output before proceeding.

2 Likes

These are pretty cool and def above my head, Thank you!

Really cool. We need to move this to a dedicated SCAD thread.