diff --git a/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java b/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java
index b4e44cac7..f3b86d98e 100644
--- a/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java
+++ b/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java
@@ -146,6 +146,12 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 
 	// If true, component change events will not be fired
 	private boolean bypassComponentChangeEvent = false;
+
+	/**
+	 * Controls the visibility of the component. If false, the component will not be rendered.
+	 * Visibility does not affect component simulation.
+	 */
+	private boolean isVisible = true;
 	
 	
 	/**
@@ -2706,7 +2712,23 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 		}
 		return false;
 	}
-	
+
+	/**
+	 * Returns true if this component is visible.
+	 * @return True if this component is visible.
+	 */
+	public boolean isVisible() {
+		return isVisible;
+	}
+
+	/**
+	 * Sets the component's visibility to the specified value.
+	 * @param value Visibility value
+	 */
+	public void setVisible(boolean value) {
+		this.isVisible = value;
+		fireComponentChangeEvent(ComponentChangeEvent.GRAPHIC_CHANGE);
+	}
 	
 	///////////  Iterators  //////////	
 	
diff --git a/swing/src/main/java/info/openrocket/swing/gui/figure3d/geometry/ComponentRenderer.java b/swing/src/main/java/info/openrocket/swing/gui/figure3d/geometry/ComponentRenderer.java
index 6b51773d4..b04a76396 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/figure3d/geometry/ComponentRenderer.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/figure3d/geometry/ComponentRenderer.java
@@ -95,6 +95,9 @@ public class ComponentRenderer {
 		if (glu == null)
 			throw new IllegalStateException(this + " Not Initialized");
 
+		if (!c.isVisible()) {
+			return;
+		}
 		glu.gluQuadricNormals(q, GLU.GLU_SMOOTH);
 
 		if (c instanceof BodyTube) {
diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/BasicFrame.java b/swing/src/main/java/info/openrocket/swing/gui/main/BasicFrame.java
index 686277cee..a32340323 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/main/BasicFrame.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/main/BasicFrame.java
@@ -52,8 +52,6 @@ import net.miginfocom.swing.MigLayout;
 
 import info.openrocket.core.file.wavefrontobj.export.OBJExportOptions;
 import info.openrocket.core.file.wavefrontobj.export.OBJExporterFactory;
-import info.openrocket.core.file.wavefrontobj.CoordTransform;
-import info.openrocket.core.file.wavefrontobj.DefaultCoordTransform;
 import info.openrocket.core.logging.ErrorSet;
 import info.openrocket.core.logging.WarningSet;
 import info.openrocket.core.appearance.DecalImage;
@@ -257,6 +255,7 @@ public class BasicFrame extends JFrame {
 
 			popupMenu.addSeparator();
 			popupMenu.add(actions.getScaleAction());
+			popupMenu.add(actions.getToggleVisibilityAction());
 
 			popupMenu.addSeparator();
 			popupMenu.add(actions.getExportOBJAction());
@@ -608,6 +607,10 @@ public class BasicFrame extends JFrame {
 		item = new JMenuItem(actions.getScaleAction());
 		editMenu.add(item);
 
+		item = new JMenuItem(actions.getToggleVisibilityAction());
+		item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_COMMA, SHORTCUT_KEY));
+		editMenu.add(item);
+
 
 		////	Preferences
 		item = new JMenuItem(trans.get("main.menu.edit.preferences"));
diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/RocketActions.java b/swing/src/main/java/info/openrocket/swing/gui/main/RocketActions.java
index 3e17f4e31..f5eee4550 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/main/RocketActions.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/main/RocketActions.java
@@ -5,11 +5,7 @@ import java.awt.Toolkit;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.io.Serial;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
@@ -18,8 +14,11 @@ import javax.swing.JOptionPane;
 import javax.swing.KeyStroke;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+
+import info.openrocket.core.rocketcomponent.*;
 import info.openrocket.swing.gui.configdialog.ComponentConfigDialog;
 import info.openrocket.swing.gui.dialogs.ScaleDialog;
+import info.openrocket.swing.gui.util.GUIUtil;
 import info.openrocket.swing.gui.util.Icons;
 
 import org.slf4j.Logger;
@@ -31,12 +30,6 @@ import info.openrocket.core.document.OpenRocketDocument;
 import info.openrocket.core.document.Simulation;
 import info.openrocket.core.l10n.Translator;
 import info.openrocket.core.logging.Markers;
-import info.openrocket.core.rocketcomponent.ComponentChangeEvent;
-import info.openrocket.core.rocketcomponent.ComponentChangeListener;
-import info.openrocket.core.rocketcomponent.ParallelStage;
-import info.openrocket.core.rocketcomponent.Rocket;
-import info.openrocket.core.rocketcomponent.RocketComponent;
-import info.openrocket.core.rocketcomponent.AxialStage;
 import info.openrocket.core.startup.Application;
 import info.openrocket.core.util.ORColor;
 import info.openrocket.core.util.Pair;
@@ -61,6 +54,8 @@ public class RocketActions {
 	public static final KeyStroke EDIT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_E,
 			Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx());
 	public static final KeyStroke DELETE_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
+	public static final KeyStroke VISIBILITY_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_COMMA,
+			Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx());
 	
 	private final OpenRocketDocument document;
 	private final Rocket rocket;
@@ -81,6 +76,7 @@ public class RocketActions {
 	private final RocketAction moveUpAction;
 	private final RocketAction moveDownAction;
 	private final RocketAction exportOBJAction;
+	private final RocketAction toggleVisibilityAction;
 	private static final Translator trans = Application.getTranslator();
 	private static final Logger log = LoggerFactory.getLogger(RocketActions.class);
 
@@ -106,6 +102,7 @@ public class RocketActions {
 		this.moveUpAction = new MoveUpAction();
 		this.moveDownAction = new MoveDownAction();
 		this.exportOBJAction = new ExportOBJAction();
+		this.toggleVisibilityAction = new ToggleVisibilityAction();
 
 		OpenRocketClipboard.addClipboardListener(new ClipboardListener() {
 			@Override
@@ -154,6 +151,7 @@ public class RocketActions {
 		moveUpAction.clipboardChanged();
 		moveDownAction.clipboardChanged();
 		exportOBJAction.clipboardChanged();
+		toggleVisibilityAction.clipboardChanged();
 	}
 	
 
@@ -207,6 +205,10 @@ public class RocketActions {
 		return exportOBJAction;
 	}
 
+	public Action getToggleVisibilityAction() {
+		return toggleVisibilityAction;
+	}
+
 	/**
 	 * Tie an action to a JButton, without using the icon or text of the action for the button.
 	 *
@@ -1257,4 +1259,83 @@ public class RocketActions {
 		}
 	}
 
+	/**
+	 * Action to toggle the visibility of the selected components.
+	 */
+	private class ToggleVisibilityAction extends RocketAction {
+		public ToggleVisibilityAction() {
+			this.putValue(NAME, trans.get("RocketActions.VisibilityAct.Hide"));
+			this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.Hide"));
+			this.putValue(SMALL_ICON, GUIUtil.getUITheme().getVisibilityHiddenIcon());
+			this.putValue(MNEMONIC_KEY, KeyEvent.VK_COMMA);
+			this.putValue(ACCELERATOR_KEY, VISIBILITY_KEY_STROKE);
+			clipboardChanged();
+		}
+
+		@Override
+		public void clipboardChanged() {
+			var components = new ArrayList<>(selectionModel.getSelectedComponents());
+			super.setEnabled(!components.isEmpty());
+
+			if (!components.isEmpty()) {
+				var firstComponent = components.get(0);
+
+				if (components.size() > 1 || isRocketOrStage(firstComponent)) {
+					if (!firstComponent.isVisible()) {
+						this.putValue(NAME, trans.get("RocketActions.VisibilityAct.ShowAll"));
+						this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.ShowAll"));
+					} else {
+						this.putValue(NAME, trans.get("RocketActions.VisibilityAct.HideAll"));
+						this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.HideAll"));
+					}
+				} else {
+					if (!firstComponent.isVisible()) {
+						this.putValue(NAME, trans.get("RocketActions.VisibilityAct.Show"));
+						this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.Show"));
+					} else {
+						this.putValue(NAME, trans.get("RocketActions.VisibilityAct.Hide"));
+						this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.Hide"));
+					}
+				}
+			}
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			var components = new ArrayList<>(selectionModel.getSelectedComponents());
+
+			if (!components.isEmpty()) {
+				var visibility = !components.get(0).isVisible();
+
+				for (var component : components) {
+					if (isRocketOrStage(component)) {
+						getAllDescendants(components).forEach(c -> c.setVisible(visibility));
+						continue;
+					}
+					component.setVisible(visibility);
+				}
+			}
+		}
+
+		private boolean isRocketOrStage(RocketComponent component) {
+			return component instanceof AxialStage || component instanceof Rocket;
+		}
+
+		private Set<RocketComponent> getAllDescendants(List<RocketComponent> components) {
+			var result = new LinkedHashSet<RocketComponent>();
+			var queue = new ArrayDeque<>(components);
+
+			while (!queue.isEmpty()) {
+				var node = queue.pop();
+				result.add(node);
+
+				for (var child : node.getChildren()) {
+					if (!result.contains(child)) {
+						queue.add(child);
+					}
+				}
+			}
+			return result;
+		}
+	}
 }
diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeRenderer.java b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeRenderer.java
index 0d4ea5ba6..e36086901 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeRenderer.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeRenderer.java
@@ -3,23 +3,16 @@ package info.openrocket.swing.gui.main.componenttree;
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Component;
-import java.awt.FlowLayout;
-import java.awt.Font;
 import java.awt.Graphics;
 import java.util.LinkedList;
 import java.util.List;
 
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
 import javax.swing.Icon;
-import javax.swing.ImageIcon;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JTree;
-import javax.swing.SwingConstants;
 import javax.swing.UIManager;
-import javax.swing.border.Border;
 import javax.swing.tree.DefaultTreeCellRenderer;
 import javax.swing.tree.TreePath;
 
@@ -35,6 +28,7 @@ import info.openrocket.core.startup.Application;
 import info.openrocket.core.unit.UnitGroup;
 import info.openrocket.core.util.ArrayList;
 import info.openrocket.core.util.TextUtil;
+import info.openrocket.swing.gui.util.Icons;
 
 @SuppressWarnings("serial")
 public class ComponentTreeRenderer extends DefaultTreeCellRenderer {
@@ -45,6 +39,7 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer {
 	private static Color textSelectionForegroundColor;
 	private static Color componentTreeBackgroundColor;
 	private static Color componentTreeForegroundColor;
+	private static Color visibilityHiddenForegroundColor;
 	private static Icon massOverrideSubcomponentIcon;
 	private static Icon massOverrideIcon;
 	private static Icon CGOverrideSubcomponentIcon;
@@ -92,6 +87,11 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer {
 		RocketComponent c = (RocketComponent) value;
 		applyToolTipText(components, c, panel);
 
+		// Set the cell text color if component is hidden
+		if (!c.isVisible() && !sel) {
+			label.setForeground(visibilityHiddenForegroundColor);
+		}
+
 		// Set the tree icon
 		final Icon treeIcon;
 		if (c.getClass().isAssignableFrom(MassComponent.class)) {
@@ -103,10 +103,11 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer {
 
 		panel.add(new JLabel(treeIcon), BorderLayout.WEST);
 
-		// Add mass/CG/CD overridden icons
+		// Add mass/CG/CD overridden and component hidden icons
 		if (c.isMassOverridden() || c.getMassOverriddenBy() != null ||
 				c.isCGOverridden() || c.getCGOverriddenBy() != null ||
-				c.isCDOverridden() || c.getCDOverriddenBy() != null) {
+				c.isCDOverridden() || c.getCDOverriddenBy() != null ||
+				!c.isVisible()) {
 			List<Icon> icons = new LinkedList<>();
 			if (c.getMassOverriddenBy() != null) {
 				icons.add(massOverrideSubcomponentIcon);
@@ -123,6 +124,9 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer {
 			} else if (c.isCDOverridden()) {
 				icons.add(CDOverrideIcon);
 			}
+			if (!c.isVisible()) {
+				icons.add(Icons.COMPONENT_HIDDEN);
+			}
 
 			Icon combinedIcon = combineIcons(3, icons.toArray(new Icon[0]));
 			JLabel overrideIconsLabel = new JLabel(combinedIcon);
@@ -144,6 +148,7 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer {
 		CGOverrideIcon = GUIUtil.getUITheme().getCGOverrideIcon();
 		CDOverrideSubcomponentIcon = GUIUtil.getUITheme().getCDOverrideSubcomponentIcon();
 		CDOverrideIcon = GUIUtil.getUITheme().getCDOverrideIcon();
+		visibilityHiddenForegroundColor = GUIUtil.getUITheme().getvisibilityHiddenForegroundColor();
 	}
 
 	private void applyToolTipText(List<RocketComponent> components, RocketComponent c, JComponent comp) {
diff --git a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketFigure.java b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketFigure.java
index fdcfb9dfa..62bfac6b9 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketFigure.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketFigure.java
@@ -265,6 +265,10 @@ public class RocketFigure extends AbstractScaleFigure {
 		while (!figureShapesCopy.isEmpty()) {
 			RocketComponentShapes rcs = figureShapesCopy.poll();
 			RocketComponent c = rcs.getComponent();
+
+			if (!c.isVisible()) {
+				continue;
+			}
 			boolean selected = false;
 			
 			// Check if component is in the selection