Implemented the part info tips overlay.
Implemented the part info tips overlay.

--- a/KerbalEngineer/Editor/BuildAdvanced.cs
+++ b/KerbalEngineer/Editor/BuildAdvanced.cs
@@ -402,9 +402,16 @@
                 GUILayout.EndHorizontal();
 
                 GUILayout.BeginHorizontal();
-                GUILayout.Label("Build Engineer Overlay:", this.settingStyle);
+                GUILayout.Label("Build Engineer Overlay (Vessel):", this.settingStyle);
                 BuildOverlay.Instance.Visible = GUILayout.Toggle(BuildOverlay.Instance.Visible, "ENABLED", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
                 BuildOverlay.Instance.Visible = !GUILayout.Toggle(!BuildOverlay.Instance.Visible, "DISABLED", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
+                GUILayout.EndHorizontal();
+
+                GUILayout.BeginHorizontal();
+                GUILayout.Label("Build Engineer Overlay (Part Info):", this.settingStyle);
+                BuildOverlayPartInfo.Visible = GUILayout.Toggle(BuildOverlayPartInfo.Visible, "VISIBLE", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
+                BuildOverlayPartInfo.NamesOnly = GUILayout.Toggle(BuildOverlayPartInfo.NamesOnly, "NAMES ONLY", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
+                BuildOverlayPartInfo.ClickToOpen = GUILayout.Toggle(BuildOverlayPartInfo.ClickToOpen, "CLICK TO OPEN", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
                 GUILayout.EndHorizontal();
 
                 GUILayout.BeginHorizontal();
@@ -424,11 +431,6 @@
                     GuiDisplaySize.Increment++;
                 }
                 GUILayout.EndHorizontal();
-
-                GUILayout.Label("Tooltip information delay: " + (BuildOverlay.Instance.TooltipInfoDelay * 1000.0f) + "ms", this.settingStyle);
-                GUI.skin = HighLogic.Skin;
-                BuildOverlay.Instance.TooltipInfoDelay = (float)Math.Round(GUILayout.HorizontalSlider(BuildOverlay.Instance.TooltipInfoDelay, 0, 2.0f), 2);
-                GUI.skin = null;
 
                 GUILayout.Label("Minimum delay between simulations: " + SimManager.minSimTime + "ms", this.settingStyle);
                 GUI.skin = HighLogic.Skin;

--- a/KerbalEngineer/Editor/BuildOverlay.cs
+++ b/KerbalEngineer/Editor/BuildOverlay.cs
@@ -22,7 +22,6 @@
 using System;
 using System.Diagnostics;
 
-using KerbalEngineer.Extensions;
 using KerbalEngineer.Settings;
 using KerbalEngineer.VesselSimulator;
 
@@ -37,15 +36,10 @@
     {
         #region Fields
 
-        private readonly Stopwatch tooltipInfoTimer = new Stopwatch();
         private GUIStyle infoStyle;
         private Stage lastStage;
 
-        private Part selectedPart;
         private GUIStyle titleStyle;
-        private float tooltipInfoDelay = 0.5f;
-        private GUIStyle tooltipInfoStyle;
-        private GUIStyle tooltipTitleStyle;
         private bool visible = true;
         private int windowId;
         private Rect windowPosition = new Rect(300.0f, 0, 0, 0);
@@ -60,11 +54,6 @@
         /// </summary>
         public static BuildOverlay Instance { get; private set; }
 
-        public float TooltipInfoDelay
-        {
-            get { return this.tooltipInfoDelay; }
-            set { this.tooltipInfoDelay = value; }
-        }
 
         /// <summary>
         ///     Gets and sets whether the display is enabled.
@@ -137,47 +126,6 @@
                 {
                     this.windowPosition.y = Screen.height - this.windowPosition.height - 5.0f;
                 }
-
-                // Find if a part is selected or being hovered over.
-                if (EditorLogic.SelectedPart != null)
-                {
-                    // Do not allow the extended information to be shown.
-                    if (this.selectedPart != null)
-                    {
-                        this.selectedPart = null;
-                        this.tooltipInfoTimer.Reset();
-                    }
-
-                    //this.DrawTooltip(EditorLogic.SelectedPart);
-                }
-                else
-                {
-                    var isPartSelected = false;
-                    foreach (var part in EditorLogic.SortedShipList)
-                    {
-                        if (part.stackIcon.highlightIcon)
-                        {
-                            // Start the extended information timer.
-                            if (part != this.selectedPart)
-                            {
-                                this.selectedPart = part;
-                                this.tooltipInfoTimer.Reset();
-                                this.tooltipInfoTimer.Start();
-                            }
-                            isPartSelected = true;
-
-                            //this.DrawTooltip(part);
-                            break;
-                        }
-                    }
-
-                    // If no part is being hovered over we must reset the extended information timer.
-                    if (!isPartSelected && this.selectedPart != null)
-                    {
-                        this.selectedPart = null;
-                        this.tooltipInfoTimer.Reset();
-                    }
-                }
             }
             catch (Exception ex)
             {
@@ -231,150 +179,6 @@
         #endregion
 
         #region Methods: private
-
-        /// <summary>
-        ///     Draws the tooltip details of the selected/highlighted part.
-        /// </summary>
-        private void DrawTooltip(Part part)
-        {
-            // Tooltip title (name of part).
-            var content = new GUIContent(part.partInfo.title);
-            var size = this.tooltipTitleStyle.CalcSize(content);
-            var position = new Rect(Event.current.mousePosition.x + 16.0f, Event.current.mousePosition.y, size.x, size.y).ClampInsideScreen();
-
-            if (position.x < Event.current.mousePosition.x + 16.0f)
-            {
-                position.y += 16.0f;
-            }
-            GUI.Label(position, content, this.tooltipTitleStyle);
-
-            // After hovering for a period of time, show extended information.
-            if (this.tooltipInfoTimer.Elapsed.TotalSeconds >= this.tooltipInfoDelay)
-            {
-                // Stop the timer as it is no longer needed.
-                if (this.tooltipInfoTimer.IsRunning)
-                {
-                    this.tooltipInfoTimer.Stop();
-                }
-
-                // Show the dry mass of the part if applicable.
-                if (part.physicalSignificance == Part.PhysicalSignificance.FULL)
-                {
-                    this.DrawTooltipInfo(ref position, "Dry Mass: " + part.GetDryMass().ToMass());
-                }
-
-                // Show resources contained within the part.
-                if (part.ContainsResources())
-                {
-                    // Show the wet mass of the part if applicable.
-                    if (part.GetResourceMass() > 0)
-                    {
-                        this.DrawTooltipInfo(ref position, "Wet Mass: " + part.GetWetMass().ToMass());
-                    }
-
-                    // List all the resources contained within the part.
-                    foreach (PartResource resource in part.Resources)
-                    {
-                        if (resource.GetDensity() > 0)
-                        {
-                            this.DrawTooltipInfo(ref position, resource.info.name + ": " + resource.GetMass().ToMass() + " (" + resource.amount + ")");
-                        }
-                        else
-                        {
-                            this.DrawTooltipInfo(ref position, resource.info.name + ": " + resource.amount);
-                        }
-                    }
-                }
-
-                // Show details for engines.
-                if (part.IsEngine())
-                {
-                    this.DrawTooltipInfo(ref position, "Maximum Thrust: " + part.GetMaxThrust().ToForce());
-                    this.DrawTooltipInfo(ref position, "Specific Impulse: " + part.GetSpecificImpulse(1f) + " / " + part.GetSpecificImpulse(0f) + "s");
-
-                    // Thrust vectoring.
-                    if (part.HasModule("ModuleGimbal"))
-                    {
-                        this.DrawTooltipInfo(ref position, "Thrust Vectoring Enabled");
-                    }
-
-                    // Contains alternator.
-                    if (part.HasModule("ModuleAlternator"))
-                    {
-                        this.DrawTooltipInfo(ref position, "Contains Alternator");
-                    }
-                }
-
-                // Show details for RCS.
-                if (part.IsRcsModule())
-                {
-                    var moduleRcs = part.GetModuleRcs();
-                    this.DrawTooltipInfo(ref position, "Thrust Power: " + moduleRcs.thrusterPower.ToForce());
-                    this.DrawTooltipInfo(ref position, "Specific Impulse: " + moduleRcs.atmosphereCurve.Evaluate(1f) + " / " + moduleRcs.atmosphereCurve.Evaluate(0f) + "s");
-                }
-
-                // Show details for solar panels.
-                if (part.IsSolarPanel())
-                {
-                    this.DrawTooltipInfo(ref position, "Charge Rate: " + part.GetModuleDeployableSolarPanel().chargeRate.ToRate());
-                }
-
-                // Show details for generators.
-                if (part.IsGenerator())
-                {
-                    foreach (var resource in part.GetModuleGenerator().inputList)
-                    {
-                        this.DrawTooltipInfo(ref position, "Input: " + resource.name + " (" + resource.rate.ToRate() + ")");
-                    }
-
-                    foreach (var resource in part.GetModuleGenerator().outputList)
-                    {
-                        this.DrawTooltipInfo(ref position, "Output: " + resource.name + " (" + resource.rate.ToRate() + ")");
-                    }
-                }
-
-                // Show details for parachutes.
-                if (part.IsParachute())
-                {
-                    var module = part.GetModuleParachute();
-                    this.DrawTooltipInfo(ref position, "Semi Deployed Drag: " + module.semiDeployedDrag);
-                    this.DrawTooltipInfo(ref position, "Fully Deployed Drag: " + module.fullyDeployedDrag);
-                    this.DrawTooltipInfo(ref position, "Deployment Altitude: " + module.deployAltitude.ToDistance());
-                }
-
-                // Contains stability augmentation system.
-                if (part.HasModule("ModuleSAS"))
-                {
-                    this.DrawTooltipInfo(ref position, "Contains SAS");
-                }
-
-                // Contains reaction wheels.
-                if (part.HasModule("ModuleReactionWheel"))
-                {
-                    this.DrawTooltipInfo(ref position, "Contains Reaction Wheels");
-                }
-
-                // Show if the part has an animation that can only be used once.
-                if (part.HasOneShotAnimation())
-                {
-                    this.DrawTooltipInfo(ref position, "Single Activation Only");
-                }
-            }
-        }
-
-        /// <summary>
-        ///     Draws a line of extended information below the previous.
-        /// </summary>
-        private void DrawTooltipInfo(ref Rect position, string value)
-        {
-            var content = new GUIContent(value);
-            var size = this.tooltipInfoStyle.CalcSize(content);
-
-            position.y += 16.0f * GuiDisplaySize.Offset;
-            position.width = size.x;
-            position.height = size.y;
-            GUI.Label(position, content, this.tooltipInfoStyle);
-        }
 
         private void InitialiseStyles()
         {
@@ -406,30 +210,12 @@
                 stretchWidth = true
             };
 
-            this.tooltipTitleStyle = new GUIStyle(HighLogic.Skin.label)
-            {
-                normal =
-                {
-                    textColor = Color.white
-                },
-                fontSize = (int)(11 * GuiDisplaySize.Offset),
-                fontStyle = FontStyle.Bold,
-                stretchWidth = true
-            };
-
-            this.tooltipInfoStyle = new GUIStyle(HighLogic.Skin.label)
-            {
-                fontSize = (int)(11 * GuiDisplaySize.Offset),
-                fontStyle = FontStyle.Bold,
-                stretchWidth = true
-            };
         }
 
         private void Load()
         {
             var handler = SettingHandler.Load("BuildOverlay.xml");
             handler.Get("visible", ref this.visible);
-            handler.Get("tooltipInfoDelay", ref this.tooltipInfoDelay);
         }
 
         private void OnSizeChanged()
@@ -443,7 +229,6 @@
         {
             var handler = new SettingHandler();
             handler.Set("visible", this.visible);
-            handler.Set("tooltipInfoDelay", this.tooltipInfoDelay);
             handler.Save("BuildOverlay.xml");
         }
 

--- /dev/null
+++ b/KerbalEngineer/Editor/BuildOverlayPartInfo.cs
@@ -1,1 +1,561 @@
-
+// 
+//     Kerbal Engineer Redux
+// 
+//     Copyright (C) 2014 CYBUTEK
+// 
+//     This program is free software: you can redistribute it and/or modify
+//     it under the terms of the GNU General Public License as published by
+//     the Free Software Foundation, either version 3 of the License, or
+//     (at your option) any later version.
+// 
+//     This program is distributed in the hope that it will be useful,
+//     but WITHOUT ANY WARRANTY; without even the implied warranty of
+//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//     GNU General Public License for more details.
+// 
+//     You should have received a copy of the GNU General Public License
+//     along with this program.  If not, see <http://www.gnu.org/licenses/>.
+// 
+
+#region Using Directives
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using KerbalEngineer.Extensions;
+using KerbalEngineer.Helpers;
+using KerbalEngineer.Settings;
+
+using UnityEngine;
+
+#endregion
+
+namespace KerbalEngineer.Editor
+{
+    [KSPAddon(KSPAddon.Startup.EditorAny, false)]
+    public class BuildOverlayPartInfo : MonoBehaviour
+    {
+        #region Fields
+
+        private static bool clickToOpen = true;
+        private static bool namesOnly;
+        private static bool visible = true;
+
+        private readonly List<InfoItem> infoItems = new List<InfoItem>();
+        private GUIStyle nameStyle;
+        private Rect position;
+        private Part selectedPart;
+        private bool showInfo;
+        private GUIStyle titleStyle;
+        private GUIStyle valueStyle;
+        private GUIStyle windowStyle;
+
+        #endregion
+
+        #region Properties
+
+        public static bool ClickToOpen
+        {
+            get { return clickToOpen; }
+            set
+            {
+                clickToOpen = value;
+                Save();
+            }
+        }
+
+        public static bool NamesOnly
+        {
+            get { return namesOnly; }
+            set
+            {
+                namesOnly = value;
+                Save();
+            }
+        }
+
+        public static bool Visible
+        {
+            get { return visible; }
+            set
+            {
+                visible = value;
+                Save();
+            }
+        }
+
+        #endregion
+
+        #region Methods: public
+
+        public static void Load()
+        {
+            var handler = SettingHandler.Load("BuildOverlayPartInfo.xml");
+            handler.GetSet("visible", ref visible);
+            handler.GetSet("namesOnly", ref namesOnly);
+            handler.GetSet("clickToOpen", ref clickToOpen);
+            handler.Save("BuildOverlayPartInfo.xml");
+        }
+
+        public static void Save()
+        {
+            var handler = SettingHandler.Load("BuildOverlayPartInfo.xml");
+            handler.Set("visible", visible);
+            handler.Set("namesOnly", namesOnly);
+            handler.Set("clickToOpen", clickToOpen);
+            handler.Save("BuildOverlayPartInfo.xml");
+        }
+
+        #endregion
+
+        #region Methods: protected
+
+        protected void OnGUI()
+        {
+            try
+            {
+                if (this.selectedPart == null || !Visible)
+                {
+                    return;
+                }
+
+                this.position = GUILayout.Window(this.GetInstanceID(), this.position, this.Window, String.Empty, this.windowStyle);
+            }
+            catch (Exception ex)
+
+            {
+                Logger.Exception(ex);
+            }
+        }
+
+        protected void Start()
+        {
+            try
+            {
+                this.InitialiseStyles();
+                Load();
+            }
+            catch (Exception ex)
+            {
+                Logger.Exception(ex);
+            }
+        }
+
+        protected void Update()
+        {
+            try
+            {
+                if (!Visible)
+                {
+                    return;
+                }
+
+                this.position.x = Mathf.Clamp(Input.mousePosition.x + 16.0f, 0.0f, Screen.width - this.position.width);
+                this.position.y = Mathf.Clamp(Screen.height - Input.mousePosition.y, 0.0f, Screen.height - this.position.height);
+                if (this.position.x < Input.mousePosition.x + 20.0f)
+                {
+                    this.position.y = Mathf.Clamp(this.position.y + 20.0f, 0.0f, Screen.height - this.position.height);
+                }
+                if (this.position.x < Input.mousePosition.x + 16.0f && this.position.y < Screen.height - Input.mousePosition.y)
+                {
+                    this.position.x = Input.mousePosition.x - 3 - this.position.width;
+                }
+
+                this.infoItems.Clear();
+                var part = EditorLogic.SortedShipList.Find(p => p.stackIcon.highlightIcon) ?? EditorLogic.SelectedPart;
+                if (part != null)
+                {
+                    if (!part.Equals(this.selectedPart))
+                    {
+                        this.selectedPart = part;
+                        this.ResetInfo();
+                    }
+                    if (NamesOnly)
+                    {
+                        return;
+                    }
+
+                    this.infoItems.Add(new InfoItem("Cost", this.selectedPart.partInfo.cost.ToString("N0")));
+                    this.SetMassItems();
+                    this.SetResourceItems();
+                    this.SetEngineInfo();
+                    this.SetAlternatorInfo();
+                    this.SetGimbalInfo();
+                    this.SetRcsInfo();
+                    this.SetParachuteInfo();
+                    this.SetSasInfo();
+                    this.SetReactionWheelInfo();
+                    this.SetSolarPanelInfo();
+                    this.SetGeneratorInfo();
+                    this.SetDecouplerInfo();
+                    this.SetTransmitterInfo();
+                    this.SetScienceExperimentInfo();
+                    this.SetScienceContainerInfo();
+                    this.SetSingleActivationInfo();
+
+                    if (!this.showInfo && Input.GetMouseButtonDown(2))
+                    {
+                        this.showInfo = true;
+                    }
+                    else if (ClickToOpen && this.showInfo && Input.GetMouseButtonDown(2))
+                    {
+                        this.ResetInfo();
+                    }
+                }
+                else
+                {
+                    this.selectedPart = null;
+                }
+            }
+            catch (Exception ex)
+            {
+                Logger.Exception(ex);
+            }
+        }
+
+        #endregion
+
+        #region Methods: private
+
+        private void InitialiseStyles()
+        {
+            this.windowStyle = new GUIStyle
+            {
+                normal =
+                {
+                    background = TextureHelper.CreateTextureFromColour(new Color(0.0f, 0.0f, 0.0f, 0.5f))
+                },
+                padding = new RectOffset(5, 5, 3, 3),
+            };
+
+            this.titleStyle = new GUIStyle
+            {
+                normal =
+                {
+                    textColor = Color.yellow
+                },
+                fontSize = (int)(11 * GuiDisplaySize.Offset),
+                fontStyle = FontStyle.Bold,
+                stretchWidth = true
+            };
+
+            this.nameStyle = new GUIStyle(this.titleStyle)
+            {
+                normal =
+                {
+                    textColor = Color.white
+                },
+                padding = new RectOffset(0, 0, 2, 0),
+                fontStyle = FontStyle.Normal
+            };
+
+            this.valueStyle = new GUIStyle(this.nameStyle)
+            {
+                alignment = TextAnchor.UpperRight
+            };
+        }
+
+        private void ResetInfo()
+        {
+            this.showInfo = !clickToOpen;
+            this.position.width = namesOnly || clickToOpen ? 0.0f : 200.0f;
+            this.position.height = 0.0f;
+        }
+
+        private void SetAlternatorInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleAlternator>())
+            {
+                return;
+            }
+
+            var alternator = this.selectedPart.GetModule<ModuleAlternator>();
+            this.infoItems.Add(new InfoItem("Alternator"));
+            foreach (var resource in alternator.outputResources)
+            {
+                this.infoItems.Add(new InfoItem("\t" + resource.name, resource.rate.ToRate()));
+            }
+        }
+
+        private void SetDecouplerInfo()
+        {
+            if (!this.selectedPart.IsDecoupler())
+            {
+                return;
+            }
+
+            var decoupler = this.selectedPart.GetProtoModuleDecoupler();
+            this.infoItems.Add(new InfoItem("Ejection Force", decoupler.EjectionForce.ToForce()));
+            if (decoupler.IsOmniDecoupler)
+            {
+                this.infoItems.Add(new InfoItem("Omni-directional"));
+            }
+        }
+
+        private void SetEngineInfo()
+        {
+            if (!this.selectedPart.IsEngine())
+            {
+                return;
+            }
+
+            var engine = this.selectedPart.GetProtoModuleEngine();
+            this.infoItems.Add(new InfoItem("Thrust", Units.ToForce(engine.MinimumThrust, engine.MaximumThrust)));
+            this.infoItems.Add(new InfoItem("Isp", Units.Concat(engine.GetSpecificImpulse(1.0f), engine.GetSpecificImpulse(0.0f)) + "s"));
+            if (engine.Propellants.Count > 0)
+            {
+                this.infoItems.Add(new InfoItem("Propellants"));
+                var totalRatio = engine.Propellants.Sum(p => p.ratio);
+                foreach (var propellant in engine.Propellants)
+                {
+                    this.infoItems.Add(new InfoItem("\t" + propellant.name, (propellant.ratio / totalRatio).ToPercent()));
+                }
+            }
+        }
+
+        private void SetGeneratorInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleGenerator>())
+            {
+                return;
+            }
+
+            var generator = this.selectedPart.GetModule<ModuleGenerator>();
+            if (generator.inputList.Count > 0)
+            {
+                this.infoItems.Add(new InfoItem("Generator Input"));
+                foreach (var resource in generator.inputList)
+                {
+                    this.infoItems.Add(new InfoItem("\t" + resource.name, resource.rate.ToRate()));
+                }
+            }
+            if (generator.outputList.Count > 0)
+            {
+                this.infoItems.Add(new InfoItem("Generator Output"));
+                foreach (var resource in generator.outputList)
+                {
+                    this.infoItems.Add(new InfoItem("\t" + resource.name, resource.rate.ToRate()));
+                }
+            }
+            if (generator.isAlwaysActive)
+            {
+                this.infoItems.Add(new InfoItem("Generator is Always Active"));
+            }
+        }
+
+        private void SetGimbalInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleGimbal>())
+            {
+                return;
+            }
+
+            var gimbal = this.selectedPart.GetModule<ModuleGimbal>();
+            this.infoItems.Add(new InfoItem("Thrust Vectoring", gimbal.gimbalRange.ToString("F2")));
+        }
+
+        private void SetMassItems()
+        {
+            if (this.selectedPart.physicalSignificance == Part.PhysicalSignificance.FULL)
+            {
+                this.infoItems.Add(new InfoItem("Mass", Units.ToMass(this.selectedPart.GetDryMass(), this.selectedPart.GetWetMass())));
+            }
+        }
+
+        private void SetParachuteInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleParachute>())
+            {
+                return;
+            }
+
+            var parachute = this.selectedPart.GetModule<ModuleParachute>();
+            this.infoItems.Add(new InfoItem("Deployed Drag", Units.Concat(parachute.semiDeployedDrag, parachute.fullyDeployedDrag)));
+            this.infoItems.Add(new InfoItem("Deployment Altitude", parachute.deployAltitude.ToDistance()));
+            this.infoItems.Add(new InfoItem("Deployment Pressure", parachute.minAirPressureToOpen.ToString("F2")));
+        }
+
+        private void SetRcsInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleRCS>())
+            {
+                return;
+            }
+
+            var rcs = this.selectedPart.GetModule<ModuleRCS>();
+            this.infoItems.Add(new InfoItem("Thruster Power", rcs.thrusterPower.ToForce()));
+            this.infoItems.Add(new InfoItem("Specific Impulse", Units.Concat(rcs.atmosphereCurve.Evaluate(1.0f), rcs.atmosphereCurve.Evaluate(0.0f)) + "s"));
+        }
+
+        private void SetReactionWheelInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleReactionWheel>())
+            {
+                return;
+            }
+
+            var reactionWheel = this.selectedPart.GetModule<ModuleReactionWheel>();
+            this.infoItems.Add(new InfoItem("Reaction Wheel Torque"));
+            this.infoItems.Add(new InfoItem("\tPitch", reactionWheel.PitchTorque.ToForce()));
+            this.infoItems.Add(new InfoItem("\tRoll", reactionWheel.RollTorque.ToForce()));
+            this.infoItems.Add(new InfoItem("\tYaw", reactionWheel.YawTorque.ToForce()));
+            foreach (var resource in reactionWheel.inputResources)
+            {
+                this.infoItems.Add(new InfoItem("\t" + resource.name, resource.rate.ToRate()));
+            }
+        }
+
+        private void SetResourceItems()
+        {
+            if (this.selectedPart.Resources.list.Any(r => !r.hideFlow))
+            {
+                this.infoItems.Add(new InfoItem("Resources"));
+                foreach (var resource in this.selectedPart.Resources.list.Where(r => !r.hideFlow))
+                {
+                    this.infoItems.Add(resource.GetDensity() > 0
+                        ? new InfoItem("\t" + resource.info.name, "(" + resource.GetMass().ToMass() + ") " + resource.amount.ToString("F1"))
+                        : new InfoItem("\t" + resource.info.name, resource.amount.ToString("F1")));
+                }
+            }
+        }
+
+        private void SetSasInfo()
+        {
+            if (this.selectedPart.HasModule<ModuleSAS>())
+            {
+                this.infoItems.Add(new InfoItem("SAS Equiped"));
+            }
+        }
+
+        private void SetScienceContainerInfo()
+        {
+            if (this.selectedPart.HasModule<ModuleScienceContainer>())
+            {
+                this.infoItems.Add(new InfoItem("Science Container"));
+            }
+        }
+
+        private void SetScienceExperimentInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleScienceExperiment>())
+            {
+                return;
+            }
+
+            var experiment = this.selectedPart.GetModule<ModuleScienceExperiment>();
+            this.infoItems.Add(new InfoItem("Science Experiment", experiment.experimentActionName));
+            this.infoItems.Add(new InfoItem("\tTransmit Efficiency", experiment.xmitDataScalar.ToPercent()));
+            if (!experiment.rerunnable)
+            {
+                this.infoItems.Add(new InfoItem("\tSingle Usage"));
+            }
+        }
+
+        private void SetSingleActivationInfo()
+        {
+            if (this.selectedPart.HasModule<ModuleAnimateGeneric>(m => m.isOneShot))
+            {
+                this.infoItems.Add(new InfoItem("Single Activation"));
+            }
+        }
+
+        private void SetSolarPanelInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleDeployableSolarPanel>())
+            {
+                return;
+            }
+
+            var solarPanel = this.selectedPart.GetModule<ModuleDeployableSolarPanel>();
+            this.infoItems.Add(new InfoItem("Charge Rate", solarPanel.chargeRate.ToRate()));
+            if (solarPanel.isBreakable)
+            {
+                this.infoItems.Add(new InfoItem("Breakable"));
+            }
+            if (solarPanel.sunTracking)
+            {
+                this.infoItems.Add(new InfoItem("Sun Tracking"));
+            }
+        }
+
+        private void SetTransmitterInfo()
+        {
+            if (!this.selectedPart.HasModule<ModuleDataTransmitter>())
+            {
+                return;
+            }
+
+            var transmitter = this.selectedPart.GetModule<ModuleDataTransmitter>();
+            this.infoItems.Add(new InfoItem("Packet Size", transmitter.packetSize.ToString("F2") + " Mits"));
+            this.infoItems.Add(new InfoItem("Bandwidth", (transmitter.packetInterval * transmitter.packetSize).ToString("F2") + "Mits/sec"));
+            this.infoItems.Add(new InfoItem(transmitter.requiredResource, transmitter.packetResourceCost.ToString("F2") + "/Packet"));
+        }
+
+        private void Window(int windowId)
+        {
+            try
+            {
+                GUILayout.Label(this.selectedPart.partInfo.title, this.titleStyle);
+                if (this.showInfo)
+                {
+                    foreach (var item in this.infoItems)
+                    {
+                        GUILayout.BeginHorizontal();
+                        if (item.Value != null)
+                        {
+                            GUILayout.Label(item.Name + ":", this.nameStyle);
+                            GUILayout.Space(25.0f);
+                            GUILayout.Label(item.Value, this.valueStyle);
+                        }
+                        else
+                        {
+                            GUILayout.Label(item.Name, this.nameStyle);
+                        }
+                        GUILayout.EndHorizontal();
+                    }
+                }
+                else if (!NamesOnly)
+                {
+                    GUILayout.Label("Click middle mouse to show more info...", this.nameStyle);
+                }
+            }
+            catch (Exception ex)
+            {
+                Logger.Exception(ex);
+            }
+        }
+
+        #endregion
+
+        #region Nested Type: InfoItem
+
+        public class InfoItem
+        {
+            #region Constructors
+
+            public InfoItem(string name)
+            {
+                this.Name = name;
+            }
+
+            public InfoItem(string name, string value)
+            {
+                this.Name = name;
+                this.Value = value;
+            }
+
+            #endregion
+
+            #region Properties
+
+            public string Name { get; set; }
+
+            public string Value { get; set; }
+
+            #endregion
+        }
+
+        #endregion
+    }
+}

--- a/KerbalEngineer/Editor/BuildPartInfo.cs
+++ /dev/null
@@ -1,252 +1,1 @@
-// 
-//     Kerbal Engineer Redux
-// 
-//     Copyright (C) 2014 CYBUTEK
-// 
-//     This program is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-// 
-//     This program is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-// 
-//     You should have received a copy of the GNU General Public License
-//     along with this program.  If not, see <http://www.gnu.org/licenses/>.
-// 
 
-#region Using Directives
-
-using System;
-using System.Collections.Generic;
-
-using KerbalEngineer.Extensions;
-
-using UnityEngine;
-
-#endregion
-
-namespace KerbalEngineer.Editor
-{
-    [KSPAddon(KSPAddon.Startup.EditorAny, false)]
-    public class BuildPartInfo : MonoBehaviour
-    {
-        #region Fields
-
-        private readonly List<InfoItem> infoItems = new List<InfoItem>();
-        private GUIStyle nameStyle;
-        private Rect position;
-        private Part selectedPart;
-        private GUIStyle titleStyle;
-        private GUIStyle valueStyle;
-        private GUIStyle windowStyle;
-
-        #endregion
-
-        #region Methods: protected
-
-        protected void OnGUI()
-        {
-            try
-            {
-                if (this.selectedPart == null)
-                {
-                    return;
-                }
-
-                this.position = GUILayout.Window(this.GetInstanceID(), this.position, this.Window, String.Empty, this.windowStyle);
-            }
-            catch (Exception ex)
-
-            {
-                Logger.Exception(ex);
-            }
-        }
-
-        protected void Start()
-        {
-            try
-            {
-                this.InitialiseStyles();
-            }
-            catch (Exception ex)
-            {
-                Logger.Exception(ex);
-            }
-        }
-
-        protected void Update()
-        {
-            try
-            {
-                this.position.x = Mathf.Clamp(Input.mousePosition.x + 16.0f, 0.0f, Screen.width - this.position.width);
-                this.position.y = this.position.x < Input.mousePosition.x + 16.0f ? Screen.height - Input.mousePosition.y + 16.0f : Mathf.Clamp(Screen.height - Input.mousePosition.y, 0.0f, Screen.height - this.position.height);
-
-                this.infoItems.Clear();
-                var part = EditorLogic.SortedShipList.Find(p => p.stackIcon.highlightIcon) ?? EditorLogic.SelectedPart;
-                if (part != null)
-                {
-                    if (!part.Equals(this.selectedPart))
-                    {
-                        this.selectedPart = part;
-                        this.position.width = 0;
-                        this.position.height = 0;
-                    }
-                    this.SetMassItems();
-                }
-                else
-                {
-                    this.selectedPart = null;
-                }
-            }
-            catch (Exception ex)
-            {
-                Logger.Exception(ex);
-            }
-        }
-
-        #endregion
-
-        #region Methods: private
-
-        private Texture2D CreateTextureFromColour(Color colour)
-        {
-            var texture = new Texture2D(1, 1, TextureFormat.ARGB32, false);
-            texture.SetPixel(1, 1, colour);
-            texture.Apply();
-            return texture;
-        }
-
-        private void InitialiseStyles()
-        {
-            this.windowStyle = new GUIStyle
-            {
-                normal =
-                {
-                    background = this.CreateTextureFromColour(new Color(0.0f, 0.0f, 0.0f, 0.5f))
-                },
-                padding = new RectOffset(5, 5, 3, 3),
-            };
-
-            this.titleStyle = new GUIStyle
-            {
-                normal =
-                {
-                    textColor = Color.yellow
-                },
-                fontSize = (int)(11 * GuiDisplaySize.Offset),
-                fontStyle = FontStyle.Bold,
-                stretchWidth = true
-            };
-
-            this.nameStyle = new GUIStyle(this.titleStyle)
-            {
-                normal =
-                {
-                    textColor = Color.white
-                },
-                padding = new RectOffset(0, 0, 2, 0),
-                fontStyle = FontStyle.Normal
-            };
-
-            this.valueStyle = new GUIStyle(this.nameStyle)
-            {
-                alignment = TextAnchor.UpperRight
-            };
-        }
-
-        private void SetMassItems()
-        {
-            if (this.selectedPart.physicalSignificance == Part.PhysicalSignificance.FULL)
-            {
-                this.infoItems.Add(new InfoItem("Dry Mass", this.selectedPart.GetDryMass().ToMass()));
-            }
-            if (this.selectedPart.ContainsResources())
-            {
-                this.infoItems.Add(new InfoItem("Wet Mass", this.selectedPart.GetWetMass().ToMass()));
-                this.SetResourceItems();
-            }
-        }
-
-        private void SetResourceItems()
-        {
-            foreach (var resource in this.selectedPart.Resources.list)
-            {
-                if (resource.GetDensity() > 0)
-                {
-                    this.infoItems.Add(new InfoItem(resource.info.name, "(" + resource.amount.ToString("F1") + ") " + resource.GetMass().ToMass()));
-                }
-                else
-                {
-                    this.infoItems.Add(new InfoItem(resource.info.name, resource.amount.ToString("F1")));
-                }
-            }
-        }
-
-        private void Window(int windowId)
-        {
-            try
-            {
-                GUILayout.Label(this.selectedPart.partInfo.title, this.titleStyle);
-                if (this.infoItems.Count > 0)
-                {
-                    GUILayout.Space(5.0f);
-                }
-                foreach (var item in this.infoItems)
-                {
-                    
-                    GUILayout.BeginHorizontal();
-                    if (item.Value != null)
-                    {
-                        GUILayout.Label(item.Name + ":", this.nameStyle);
-                        GUILayout.Space(10.0f);
-                        GUILayout.Label(item.Value, this.valueStyle);
-                    }
-                    else
-                    {
-                        GUILayout.Label(item.Name, this.nameStyle);
-                    }
-                    GUILayout.EndHorizontal();
-                }
-            }
-            catch (Exception ex)
-            {
-                Logger.Exception(ex);
-            }
-        }
-
-        #endregion
-
-        #region Nested Type: InfoItem
-
-        public class InfoItem
-        {
-            #region Constructors
-
-            public InfoItem(string name)
-            {
-                this.Name = name;
-            }
-
-            public InfoItem(string name, string value)
-            {
-                this.Name = name;
-                this.Value = value;
-            }
-
-            #endregion
-
-            #region Properties
-
-            public string Name { get; set; }
-
-            public string Value { get; set; }
-
-            #endregion
-        }
-
-        #endregion
-    }
-}

--- a/KerbalEngineer/Extensions/PartExtensions.cs
+++ b/KerbalEngineer/Extensions/PartExtensions.cs
@@ -29,28 +29,65 @@
 {
     public static class PartExtensions
     {
-        /// <summary>
-        ///     Gets whether the part contains a PartModule.
-        /// </summary>
-        public static bool HasModule<T>(this Part part)
-        {
-            return part.Modules.OfType<T>().Any();
-        }
-
-        /// <summary>
-        ///     Gets whether the part contains a PartModule.
-        /// </summary>
-        public static bool HasModule(this Part part, string className)
-        {
-            return part.Modules.Contains(className);
-        }
-
-        /// <summary>
-        ///     Gets whether the part contains a PartModule.
-        /// </summary>
-        public static bool HasModule(this Part part, int moduleId)
-        {
-            return part.Modules.Contains(moduleId);
+        #region Methods: public
+
+        public static bool ContainsResource(this Part part, int resourceId)
+        {
+            return part.Resources.Contains(resourceId);
+        }
+
+        /// <summary>
+        ///     Gets whether the part contains resources.
+        /// </summary>
+        public static bool ContainsResources(this Part part)
+        {
+            return part.Resources.list.Count(p => p.amount > 0d) > 0;
+        }
+
+        /// <summary>
+        ///     Gets whether the part has fuel.
+        /// </summary>
+        public static bool EngineHasFuel(this Part part)
+        {
+            if (part.HasModule<ModuleEngines>())
+            {
+                return part.GetModuleEngines().getFlameoutState;
+            }
+            if (part.HasModule<MultiModeEngine>())
+            {
+                return part.GetModuleMultiModeEngine().getFlameoutState;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        ///     Gets the dry mass of the part.
+        /// </summary>
+        public static double GetDryMass(this Part part)
+        {
+            return (part.physicalSignificance == Part.PhysicalSignificance.FULL) ? part.mass : 0d;
+        }
+
+        /// <summary>
+        ///     Gets the maximum thrust of the part if it's an engine.
+        /// </summary>
+        public static double GetMaxThrust(this Part part)
+        {
+            if (part.HasModule<ModuleEngines>())
+            {
+                return part.GetModuleEngines().maxThrust;
+            }
+            if (part.HasModule<MultiModeEngine>())
+            {
+                return part.GetModuleMultiModeEngine().maxThrust;
+            }
+            if (part.HasModule<ModuleEnginesFX>())
+            {
+                return part.GetModuleEnginesFx().maxThrust;
+            }
+
+            return 0d;
         }
 
         /// <summary>
@@ -77,9 +114,20 @@
             return (T)Convert.ChangeType(part.Modules[classId], typeof(T));
         }
 
-        public static List<T> GetModules<T>(this Part part) where T : PartModule
-        {
-            return part.Modules.OfType<T>().ToList();
+        /// <summary>
+        ///     Gets a ModuleAlternator typed PartModule.
+        /// </summary>
+        public static ModuleAlternator GetModuleAlternator(this Part part)
+        {
+            return part.GetModule<ModuleAlternator>();
+        }
+
+        /// <summary>
+        ///     Gets a ModuleDeployableSolarPanel typed PartModule.
+        /// </summary>
+        public static ModuleDeployableSolarPanel GetModuleDeployableSolarPanel(this Part part)
+        {
+            return part.GetModule<ModuleDeployableSolarPanel>();
         }
 
         /// <summary>
@@ -90,58 +138,109 @@
             return part.GetModule<ModuleEngines>();
         }
 
+        public static ModuleEnginesFX GetModuleEnginesFx(this Part part)
+        {
+            return part.GetModule<ModuleEnginesFX>();
+        }
+
+        /// <summary>
+        ///     Gets a ModuleGenerator typed PartModule.
+        /// </summary>
+        public static ModuleGenerator GetModuleGenerator(this Part part)
+        {
+            return part.GetModule<ModuleGenerator>();
+        }
+
+        /// <summary>
+        ///     Gets a ModuleGimbal typed PartModule.
+        /// </summary>
+        public static ModuleGimbal GetModuleGimbal(this Part part)
+        {
+            return part.GetModule<ModuleGimbal>();
+        }
+
         /// <summary>
         ///     Gets the current selected ModuleEnginesFX.
         /// </summary>
-        public static ModuleEnginesFX GetModuleEnginesFx(this Part part)
+        public static ModuleEnginesFX GetModuleMultiModeEngine(this Part part)
         {
             var mode = part.GetModule<MultiModeEngine>().mode;
             return part.Modules.OfType<ModuleEnginesFX>().FirstOrDefault(engine => engine.engineID == mode);
         }
 
+        /// <summary>
+        ///     Gets a ModuleParachute typed PartModule.
+        /// </summary>
+        public static ModuleParachute GetModuleParachute(this Part part)
+        {
+            return part.GetModule<ModuleParachute>();
+        }
+
         public static ModuleRCS GetModuleRcs(this Part part)
         {
             return part.GetModule<ModuleRCS>();
         }
 
         /// <summary>
-        ///     Gets a ModuleGimbal typed PartModule.
-        /// </summary>
-        public static ModuleGimbal GetModuleGimbal(this Part part)
-        {
-            return part.GetModule<ModuleGimbal>();
-        }
-
-        /// <summary>
-        ///     Gets a ModuleDeployableSolarPanel typed PartModule.
-        /// </summary>
-        public static ModuleDeployableSolarPanel GetModuleDeployableSolarPanel(this Part part)
-        {
-            return part.GetModule<ModuleDeployableSolarPanel>();
-        }
-
-        /// <summary>
-        ///     Gets a ModuleAlternator typed PartModule.
-        /// </summary>
-        public static ModuleAlternator GetModuleAlternator(this Part part)
-        {
-            return part.GetModule<ModuleAlternator>();
-        }
-
-        /// <summary>
-        ///     Gets a ModuleGenerator typed PartModule.
-        /// </summary>
-        public static ModuleGenerator GetModuleGenerator(this Part part)
-        {
-            return part.GetModule<ModuleGenerator>();
-        }
-
-        /// <summary>
-        ///     Gets a ModuleParachute typed PartModule.
-        /// </summary>
-        public static ModuleParachute GetModuleParachute(this Part part)
-        {
-            return part.GetModule<ModuleParachute>();
+        ///     Gets a typed list of PartModules.
+        /// </summary>
+        public static List<T> GetModules<T>(this Part part) where T : PartModule
+        {
+            return part.Modules.OfType<T>().ToList();
+        }
+
+        /// <summary>
+        ///     Gets a generic proto engine for the current engine module attached to the part.
+        /// </summary>
+        public static ProtoModuleEngine GetProtoModuleEngine(this Part part)
+        {
+            if (HasModule<ModuleEngines>(part))
+            {
+                return new ProtoModuleEngine(GetModule<ModuleEngines>(part));
+            }
+            if (HasModule<MultiModeEngine>(part))
+            {
+                return new ProtoModuleEngine(GetModuleMultiModeEngine(part));
+            }
+            if (HasModule<ModuleEnginesFX>(part))
+            {
+                return new ProtoModuleEngine(GetModule<ModuleEnginesFX>(part));
+            }
+            return null;
+        }
+
+        /// <summary>
+        ///     Gets the current specific impulse for the engine.
+        /// </summary>
+        public static double GetSpecificImpulse(this Part part, float atmosphere)
+        {
+            if (part.HasModule<ModuleEngines>())
+            {
+                return part.GetModuleEngines().atmosphereCurve.Evaluate(atmosphere);
+            }
+            if (part.HasModule<MultiModeEngine>())
+            {
+                return part.GetModuleMultiModeEngine().atmosphereCurve.Evaluate(atmosphere);
+            }
+            if (part.HasModule<ModuleEnginesFX>())
+            {
+                return part.GetModuleEnginesFx().atmosphereCurve.Evaluate(atmosphere);
+            }
+
+            return 0d;
+        }
+
+        public static ProtoModuleDecoupler GetProtoModuleDecoupler(this Part part)
+        {
+            if (HasModule<ModuleDecouple>(part))
+            {
+                return new ProtoModuleDecoupler(GetModule<ModuleDecouple>(part));
+            }
+            if (HasModule<ModuleAnchoredDecoupler>(part))
+            {
+                return new ProtoModuleDecoupler(GetModule<ModuleAnchoredDecoupler>(part));
+            }
+            return null;
         }
 
         /// <summary>
@@ -153,123 +252,153 @@
         }
 
         /// <summary>
-        ///     Gets the dry mass of the part.
-        /// </summary>
-        public static double GetDryMass(this Part part)
-        {
-            return (part.physicalSignificance == Part.PhysicalSignificance.FULL) ? part.mass : 0d;
-        }
-
-        /// <summary>
-        ///     Gets the maximum thrust of the part if it's an engine.
-        /// </summary>
-        public static double GetMaxThrust(this Part part)
-        {
-            if (part.HasModule<ModuleEngines>())
-            {
-                return part.GetModuleEngines().maxThrust;
-            }
-            if (part.HasModule<MultiModeEngine>())
-            {
-                return part.GetModuleEnginesFx().maxThrust;
-            }
-
-            return 0d;
-        }
-
-        /// <summary>
-        ///     Gets the current specific impulse for the engine.
-        /// </summary>
-        public static double GetSpecificImpulse(this Part part, float atmosphere)
-        {
-            if (part.HasModule<ModuleEngines>())
-            {
-                return part.GetModuleEngines().atmosphereCurve.Evaluate(atmosphere);
-            }
-            if (part.HasModule<MultiModeEngine>())
-            {
-                return part.GetModuleEnginesFx().atmosphereCurve.Evaluate(atmosphere);
-            }
-
-            return 0d;
-        }
-
-        /// <summary>
-        ///     Gets whether the part has fuel.
-        /// </summary>
-        public static bool EngineHasFuel(this Part part)
-        {
-            if (part.HasModule<ModuleEngines>())
-            {
-                return part.GetModuleEngines().getFlameoutState;
-            }
-            if (part.HasModule<MultiModeEngine>())
-            {
-                return part.GetModuleEnginesFx().getFlameoutState;
+        ///     Gets whether the part contains a PartModule.
+        /// </summary>
+        public static bool HasModule<T>(this Part part) where T : PartModule
+        {
+            return part.Modules.OfType<T>().Any();
+        }
+
+        /// <summary>
+        ///     Gets whether the part contains a PartModule conforming to the supplied predicate.
+        /// </summary>
+        public static bool HasModule<T>(this Part part, Func<T, bool> predicate) where T : PartModule
+        {
+            return part.Modules.OfType<T>().Any(predicate);
+        }
+
+        /// <summary>
+        ///     Gets whether the part contains a PartModule.
+        /// </summary>
+        public static bool HasModule(this Part part, string className)
+        {
+            return part.Modules.Contains(className);
+        }
+
+        /// <summary>
+        ///     Gets whether the part contains a PartModule.
+        /// </summary>
+        public static bool HasModule(this Part part, int moduleId)
+        {
+            return part.Modules.Contains(moduleId);
+        }
+
+        /// <summary>
+        ///     Gets whether the part has a one shot animation.
+        /// </summary>
+        public static bool HasOneShotAnimation(this Part part)
+        {
+            return part.HasModule<ModuleAnimateGeneric>() && part.GetModule<ModuleAnimateGeneric>().isOneShot;
+        }
+
+        /// <summary>
+        ///     Gets whether the part is a command module.
+        /// </summary>
+        public static bool IsCommandModule(this Part part)
+        {
+            return part.HasModule<ModuleCommand>();
+        }
+
+        /// <summary>
+        ///     Gets whether the part is decoupled in a specified stage.
+        /// </summary>
+        public static bool IsDecoupledInStage(this Part part, int stage)
+        {
+            if ((part.IsDecoupler() || part.IsLaunchClamp()) && part.inverseStage == stage)
+            {
+                return true;
+            }
+            if (part.parent == null)
+            {
+                return false;
+            }
+            return part.parent.IsDecoupledInStage(stage);
+        }
+
+        /// <summary>
+        ///     Gets whether the part is a decoupler.
+        /// </summary>
+        public static bool IsDecoupler(this Part part)
+        {
+            return part.HasModule<ModuleDecouple>() || part.HasModule<ModuleAnchoredDecoupler>();
+        }
+
+        /// <summary>
+        ///     Gets whether the part is an active engine.
+        /// </summary>
+        public static bool IsEngine(this Part part)
+        {
+            return part.HasModule<ModuleEngines>() || part.HasModule<ModuleEnginesFX>();
+        }
+
+        /// <summary>
+        ///     Gets whether the part is a fuel line.
+        /// </summary>
+        public static bool IsFuelLine(this Part part)
+        {
+            return (part is FuelLine);
+        }
+
+        /// <summary>
+        ///     Gets whether the part is a generator.
+        /// </summary>
+        public static bool IsGenerator(this Part part)
+        {
+            return part.HasModule<ModuleGenerator>();
+        }
+
+        /// <summary>
+        ///     Gets whether the part is a launch clamp.
+        /// </summary>
+        public static bool IsLaunchClamp(this Part part)
+        {
+            return part.HasModule<LaunchClamp>();
+        }
+
+        /// <summary>
+        ///     Gets whether the part is a parachute.
+        /// </summary>
+        public static bool IsParachute(this Part part)
+        {
+            return part.HasModule<ModuleParachute>();
+        }
+
+        /// <summary>
+        ///     Gets whether the part is considered a primary part on the vessel.
+        /// </summary>
+        public static bool IsPrimary(this Part part, List<Part> partsList, PartModule module)
+        {
+            foreach (var vesselPart in partsList)
+            {
+                if (!vesselPart.HasModule(module.ClassID))
+                {
+                    continue;
+                }
+
+                if (vesselPart == part)
+                {
+                    return true;
+                }
+                break;
             }
 
             return false;
         }
 
-        /// <summary>
-        ///     Gets whether the part contains resources.
-        /// </summary>
-        public static bool ContainsResources(this Part part)
-        {
-            return part.Resources.list.Count(p => p.amount > 0d) > 0;
-        }
-
-        public static bool ContainsResource(this Part part, int resourceId)
-        {
-            return part.Resources.Contains(resourceId);
-        }
-
-        /// <summary>
-        ///     Gets whether the part is a decoupler.
-        /// </summary>
-        public static bool IsDecoupler(this Part part)
-        {
-            return part.HasModule<ModuleDecouple>() || part.HasModule<ModuleAnchoredDecoupler>();
-        }
-
-        /// <summary>
-        ///     Gets whether the part is decoupled in a specified stage.
-        /// </summary>
-        public static bool IsDecoupledInStage(this Part part, int stage)
-        {
-            if ((part.IsDecoupler() || part.IsLaunchClamp()) && part.inverseStage == stage)
-            {
-                return true;
-            }
-            if (part.parent == null)
-            {
-                return false;
-            }
-            return part.parent.IsDecoupledInStage(stage);
-        }
-
-        /// <summary>
-        ///     Gets whether the part is a launch clamp.
-        /// </summary>
-        public static bool IsLaunchClamp(this Part part)
-        {
-            return part.HasModule<LaunchClamp>();
-        }
-
-        /// <summary>
-        ///     Gets whether the part is an active engine.
-        /// </summary>
-        public static bool IsEngine(this Part part)
-        {
-            return part.HasModule<ModuleEngines>() || part.HasModule<MultiModeEngine>();
-        }
-
         public static bool IsRcsModule(this Part part)
         {
             return part.HasModule<ModuleRCS>();
         }
 
         /// <summary>
+        ///     Gets whether the part is a sepratron.
+        /// </summary>
+        public static bool IsSepratron(this Part part)
+        {
+            return (part.IsSolidRocket() && part.ActivatesEvenIfDisconnected && part.IsDecoupledInStage(part.inverseStage));
+        }
+
+        /// <summary>
         ///     Gets whether the part is a deployable solar panel.
         /// </summary>
         public static bool IsSolarPanel(this Part part)
@@ -278,30 +407,6 @@
         }
 
         /// <summary>
-        ///     Gets whether the part is a generator.
-        /// </summary>
-        public static bool IsGenerator(this Part part)
-        {
-            return part.HasModule<ModuleGenerator>();
-        }
-
-        /// <summary>
-        ///     Gets whether the part is a command module.
-        /// </summary>
-        public static bool IsCommandModule(this Part part)
-        {
-            return part.HasModule<ModuleCommand>();
-        }
-
-        /// <summary>
-        ///     Gets whether the part is a parachute.
-        /// </summary>
-        public static bool IsParachute(this Part part)
-        {
-            return part.HasModule<ModuleParachute>();
-        }
-
-        /// <summary>
         ///     Gets whether the part is a solid rocket motor.
         /// </summary>
         public static bool IsSolidRocket(this Part part)
@@ -309,50 +414,157 @@
             return part.HasModule<ModuleEngines>() && part.GetModuleEngines().throttleLocked;
         }
 
-        /// <summary>
-        ///     Gets whether the part is a sepratron.
-        /// </summary>
-        public static bool IsSepratron(this Part part)
-        {
-            return (part.IsSolidRocket() && part.ActivatesEvenIfDisconnected && part.IsDecoupledInStage(part.inverseStage));
-        }
-
-        /// <summary>
-        ///     Gets whether the part is a fuel line.
-        /// </summary>
-        public static bool IsFuelLine(this Part part)
-        {
-            return (part is FuelLine);
-        }
-
-        /// <summary>
-        ///     Gets whether the part is considered a primary part on the vessel.
-        /// </summary>
-        public static bool IsPrimary(this Part part, List<Part> partsList, PartModule module)
-        {
-            foreach (var vesselPart in partsList)
-            {
-                if (!vesselPart.HasModule(module.ClassID))
-                {
-                    continue;
-                }
-
-                if (vesselPart == part)
-                {
-                    return true;
-                }
-                break;
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        ///     Gets whether the part has a one shot animation.
-        /// </summary>
-        public static bool HasOneShotAnimation(this Part part)
-        {
-            return part.HasModule<ModuleAnimateGeneric>() && part.GetModule<ModuleAnimateGeneric>().isOneShot;
-        }
+        #endregion
+
+        #region Nested Type: ProtoModuleDecoupler
+
+        public class ProtoModuleDecoupler
+        {
+            #region Fields
+
+            private readonly PartModule module;
+
+            #endregion
+
+            #region Constructors
+
+            public ProtoModuleDecoupler(PartModule module)
+            {
+                this.module = module;
+
+                if (this.module is ModuleDecouple)
+                {
+                    this.SetModuleDecouple();
+                }
+                else if (this.module is ModuleAnchoredDecoupler)
+                {
+                    this.SetModuleAnchoredDecoupler();
+                }
+            }
+
+            #endregion
+
+            #region Properties
+
+            public double EjectionForce { get; private set; }
+            public bool IsOmniDecoupler { get; private set; }
+
+            #endregion
+
+            #region Methods: private
+
+            private void SetModuleAnchoredDecoupler()
+            {
+                var decoupler = this.module as ModuleAnchoredDecoupler;
+                if (decoupler == null)
+                {
+                    return;
+                }
+
+                this.EjectionForce = decoupler.ejectionForce;
+            }
+
+            private void SetModuleDecouple()
+            {
+                var decoupler = this.module as ModuleDecouple;
+                if (decoupler == null)
+                {
+                    return;
+                }
+
+                this.EjectionForce = decoupler.ejectionForce;
+                this.IsOmniDecoupler = decoupler.isOmniDecoupler;
+            }
+
+            #endregion
+        }
+
+        #endregion
+
+        #region Nested Type: ProtoModuleEngine
+
+        public class ProtoModuleEngine
+        {
+            #region Fields
+
+            private readonly PartModule module;
+
+            #endregion
+
+            #region Constructors
+
+            public ProtoModuleEngine(PartModule module)
+            {
+                this.module = module;
+
+                if (module is ModuleEngines)
+                {
+                    this.SetModuleEngines();
+                }
+                else if (module is ModuleEnginesFX)
+                {
+                    this.SetModuleEnginesFx();
+                }
+            }
+
+            #endregion
+
+            #region Properties
+
+            public double MaximumThrust { get; private set; }
+            public double MinimumThrust { get; private set; }
+            public List<Propellant> Propellants { get; private set; }
+
+            #endregion
+
+            #region Methods: public
+
+            public float GetSpecificImpulse(float atmosphere)
+            {
+                if (this.module is ModuleEngines)
+                {
+                    return (this.module as ModuleEngines).atmosphereCurve.Evaluate(atmosphere);
+                }
+                if (this.module is ModuleEnginesFX)
+                {
+                    return (this.module as ModuleEnginesFX).atmosphereCurve.Evaluate(atmosphere);
+                }
+                return 0.0f;
+            }
+
+            #endregion
+
+            #region Methods: private
+
+            private void SetModuleEngines()
+            {
+                var engine = this.module as ModuleEngines;
+                if (engine == null)
+                {
+                    return;
+                }
+
+                this.MaximumThrust = engine.maxThrust * (engine.thrustPercentage * 0.01);
+                this.MinimumThrust = engine.minThrust;
+                this.Propellants = engine.propellants;
+            }
+
+            private void SetModuleEnginesFx()
+            {
+                var engine = this.module as ModuleEnginesFX;
+                if (engine == null)
+                {
+                    return;
+                }
+
+                this.MaximumThrust = engine.maxThrust * (engine.thrustPercentage * 0.01);
+                this.MinimumThrust = engine.minThrust;
+                this.Propellants = engine.propellants;
+            }
+
+            #endregion
+        }
+
+        #endregion
     }
 }

--- /dev/null
+++ b/KerbalEngineer/Helpers/TextureHelper.cs
@@ -1,1 +1,42 @@
+// 
+//     Kerbal Engineer Redux
+// 
+//     Copyright (C) 2014 CYBUTEK
+// 
+//     This program is free software: you can redistribute it and/or modify
+//     it under the terms of the GNU General Public License as published by
+//     the Free Software Foundation, either version 3 of the License, or
+//     (at your option) any later version.
+// 
+//     This program is distributed in the hope that it will be useful,
+//     but WITHOUT ANY WARRANTY; without even the implied warranty of
+//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//     GNU General Public License for more details.
+// 
+//     You should have received a copy of the GNU General Public License
+//     along with this program.  If not, see <http://www.gnu.org/licenses/>.
+// 
 
+#region Using Directives
+
+using UnityEngine;
+
+#endregion
+
+namespace KerbalEngineer.Helpers
+{
+    public static class TextureHelper
+    {
+        #region Methods: public
+
+        public static Texture2D CreateTextureFromColour(Color colour)
+        {
+            var texture = new Texture2D(1, 1, TextureFormat.ARGB32, false);
+            texture.SetPixel(1, 1, colour);
+            texture.Apply();
+            return texture;
+        }
+
+        #endregion
+    }
+}

--- a/KerbalEngineer/Helpers/Units.cs
+++ b/KerbalEngineer/Helpers/Units.cs
@@ -75,13 +75,14 @@
 
         public static string ToForce(double value)
         {
-            return value.ToString((value < 100000.0) ? (value < 10000.0) ? (value < 100.0) ? "N3" : "N2" : "N1" : "N0") + "kN";
+            return value.ToString((value < 100000.0) ? (value < 10000.0) ? (value < 100.0) ? (Math.Abs(value) < Double.Epsilon) ? "N0" : "N3" : "N2" : "N1" : "N0") + "kN";
         }
 
         public static string ToForce(double value1, double value2)
         {
-            var format = (value1 < 100000.0) ? (value1 < 10000.0) ? (value1 < 100.0) ? "N3" : "N2" : "N1" : "N0";
-            return value1.ToString(format) + " / " + value2.ToString(format) + "kN";
+            var format1 = (value1 < 100000.0) ? (value1 < 10000.0) ? (value1 < 100.0) ? (Math.Abs(value1) < Double.Epsilon) ? "N0" : "N3" : "N2" : "N1" : "N0";
+            var format2 = (value2 < 100000.0) ? (value2 < 10000.0) ? (value2 < 100.0) ? (Math.Abs(value2) < Double.Epsilon) ? "N0" : "N3" : "N2" : "N1" : "N0";
+            return value1.ToString(format1) + " / " + value2.ToString(format2) + "kN";
         }
 
         public static string ToMass(double value, int decimals = 0)
@@ -105,7 +106,7 @@
 
         public static string ToRate(double value, int decimals = 1)
         {
-            return value > 0 ? value.ToString("F" + decimals) + "/sec" : (value * 60.0).ToString("F" + decimals);
+            return value < 1.0 ? (value * 60.0).ToString("F" + decimals) + "/min" : value.ToString("F" + decimals) + "/sec";
         }
 
         public static string ToSpeed(double value, int decimals = 2)
@@ -122,6 +123,11 @@
             return TimeFormatter.ConvertToString(value);
         }
 
+        public static string Concat(double value1, double value2, int decimals = 1)
+        {
+            return value1.ToString("F" + decimals) + " / " + value2.ToString("F" + decimals);
+        }
+
         #endregion
     }
 }

--- a/KerbalEngineer/KerbalEngineer.csproj
+++ b/KerbalEngineer/KerbalEngineer.csproj
@@ -59,7 +59,7 @@
     <Compile Include="Editor\BuildAdvanced.cs" />
     <Compile Include="Editor\BuildOverlay.cs" />
     <Compile Include="CelestialBodies.cs" />
-    <Compile Include="Editor\BuildPartInfo.cs" />
+    <Compile Include="Editor\BuildOverlayPartInfo.cs" />
     <Compile Include="Editor\BuildToolbar.cs" />
     <Compile Include="Extensions\FloatExtensions.cs" />
     <Compile Include="Extensions\OrbitExtensions.cs" />
@@ -98,6 +98,7 @@
     <Compile Include="Flight\Presets\PresetLibrary.cs" />
     <Compile Include="GuiDisplaySize.cs" />
     <Compile Include="Helpers\AngleHelper.cs" />
+    <Compile Include="Helpers\TextureHelper.cs" />
     <Compile Include="Helpers\Units.cs" />
     <Compile Include="Helpers\TimeFormatter.cs" />
     <Compile Include="UIControls\DropDown.cs" />

 Binary files a/Output/KerbalEngineer/KerbalEngineer.dll and b/Output/KerbalEngineer/KerbalEngineer.dll differ
 Binary files a/Release/KerbalEngineer-1.0.8.1.zip and b/Release/KerbalEngineer-1.0.8.1.zip differ