From 0e437a8cb7e938cf2a38b8d3eed9bf2adbfb13eb Mon Sep 17 00:00:00 2001
From: JoePfeiffer <joseph@pfeifferfamily.net>
Date: Fri, 26 Jul 2024 06:44:24 -0600
Subject: [PATCH 1/4] Split UniqueID functionality into new ModID class and
 java UUID

---
 .../AbstractAerodynamicCalculator.java        |  5 +-
 .../core/aerodynamics/AerodynamicForces.java  | 43 ++++++++-------
 .../aerodynamics/BarrowmanCalculator.java     |  5 +-
 .../core/aerodynamics/FlightConditions.java   | 20 +++----
 .../core/document/OpenRocketDocument.java     | 27 +++++----
 .../openrocket/core/document/Simulation.java  | 17 +++---
 .../importt/FlightDataBranchHandler.java      |  3 +-
 .../openrocket/core/logging/MessageSet.java   |  7 ++-
 .../core/masscalc/MassCalculator.java         | 12 ++--
 .../atmosphere/AtmosphericConditions.java     | 12 ++--
 .../models/atmosphere/ExtendedISAModel.java   |  5 +-
 .../core/models/gravity/WGSGravityModel.java  |  5 +-
 .../core/models/wind/PinkNoiseWindModel.java  |  5 +-
 .../core/motor/MotorConfiguration.java        |  7 ++-
 .../FlightConfigurationModifier.java          |  5 +-
 .../modifiers/GenericComponentModifier.java   |  6 +-
 .../core/rocketcomponent/BodyTube.java        |  6 +-
 .../rocketcomponent/FlightConfiguration.java  | 34 ++++++------
 .../core/rocketcomponent/InnerTube.java       |  2 +
 .../core/rocketcomponent/MotorMount.java      |  3 +-
 .../core/rocketcomponent/Rocket.java          | 35 ++++++------
 .../core/rocketcomponent/RocketComponent.java | 25 +++++----
 .../core/simulation/EventQueue.java           | 15 ++---
 .../core/simulation/FlightDataBranch.java     | 11 ++--
 .../core/simulation/SimulationConditions.java | 48 +++++++---------
 .../core/simulation/SimulationStatus.java     | 55 +++++++++----------
 .../listeners/SimulationListenerHelper.java   | 49 +++++++++--------
 .../openrocket/core/startup/Preferences.java  | 15 -----
 .../openrocket/core/unit/CaliberUnit.java     |  5 +-
 .../core/unit/PercentageOfLengthUnit.java     |  7 ++-
 .../core/util/{UniqueID.java => ModID.java}   | 32 +++++++----
 .../openrocket/core/util/Monitorable.java     |  4 +-
 .../openrocket/core/util/MonitorableSet.java  | 20 ++++---
 .../FlightConfigurationTest.java              | 17 +++---
 .../info/openrocket/core/util/ModIDTest.java  | 23 ++++++++
 .../openrocket/core/util/UniqueIDTest.java    | 28 ----------
 .../componenttree/ComponentTreeModel.java     |  5 +-
 .../swing/gui/scalefigure/FinPointFigure.java |  3 +-
 .../swing/gui/scalefigure/RocketPanel.java    |  3 +-
 .../openrocket/swing/IntegrationTest.java     |  3 +-
 40 files changed, 325 insertions(+), 307 deletions(-)
 rename core/src/main/java/info/openrocket/core/util/{UniqueID.java => ModID.java} (53%)
 create mode 100644 core/src/test/java/info/openrocket/core/util/ModIDTest.java
 delete mode 100644 core/src/test/java/info/openrocket/core/util/UniqueIDTest.java

diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/AbstractAerodynamicCalculator.java b/core/src/main/java/info/openrocket/core/aerodynamics/AbstractAerodynamicCalculator.java
index 340db663b..59538069c 100644
--- a/core/src/main/java/info/openrocket/core/aerodynamics/AbstractAerodynamicCalculator.java
+++ b/core/src/main/java/info/openrocket/core/aerodynamics/AbstractAerodynamicCalculator.java
@@ -6,6 +6,7 @@ import info.openrocket.core.logging.WarningSet;
 import info.openrocket.core.rocketcomponent.FlightConfiguration;
 import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Coordinate;
 
 /**
@@ -28,8 +29,8 @@ public abstract class AbstractAerodynamicCalculator implements AerodynamicCalcul
 	protected WarningSet ignoreWarningSet = new WarningSet();
 
 	/** The aerodynamic modification ID of the latest rocket */
-	private int rocketAeroModID = -1;
-	private int rocketTreeModID = -1;
+	private ModID rocketAeroModID = new ModID();
+	private ModID rocketTreeModID = new ModID();
 
 	//////////////// Aerodynamic calculators ////////////////
 
diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java b/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java
index 361ceeb97..97fd05c04 100644
--- a/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java
+++ b/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java
@@ -5,6 +5,7 @@ import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.util.BugException;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Monitorable;
 
 public class AerodynamicForces implements Cloneable, Monitorable {
@@ -67,7 +68,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	private double pitchDampingMoment = Double.NaN;
 	private double yawDampingMoment = Double.NaN;
 
-	private int modID = 0;
+	private ModID modID = ModID.INVALID;
 
 	private boolean axisymmetric = true;
 
@@ -87,7 +88,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	 */
 	public void setComponent(RocketComponent component) {
 		this.component = component;
-		modID++;
+		modID = new ModID();
 	}
 
 	/**
@@ -100,7 +101,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCP(Coordinate cp) {
 		this.cp = cp;
-		modID++;
+		modID = new ModID();
 	}
 
 	public Coordinate getCP() {
@@ -109,7 +110,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCNa(double cNa) {
 		CNa = cNa;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCNa() {
@@ -118,7 +119,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCN(double cN) {
 		CN = cN;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCN() {
@@ -127,7 +128,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCm(double cm) {
 		Cm = cm;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCm() {
@@ -136,7 +137,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCside(double cside) {
 		Cside = cside;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCside() {
@@ -145,7 +146,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCyaw(double cyaw) {
 		Cyaw = cyaw;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCyaw() {
@@ -154,7 +155,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCroll(double croll) {
 		Croll = croll;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCroll() {
@@ -163,7 +164,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCrollDamp(double crollDamp) {
 		CrollDamp = crollDamp;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCrollDamp() {
@@ -172,7 +173,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCrollForce(double crollForce) {
 		CrollForce = crollForce;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCrollForce() {
@@ -181,7 +182,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCDaxial(double cdaxial) {
 		CDaxial = cdaxial;
-		modID++;
+		modID= new ModID();
 	}
 
 	public double getCDaxial() {
@@ -190,7 +191,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setCD(double cD) {
 		CD = cD;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getCD() {
@@ -206,7 +207,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setPressureCD(double pressureCD) {
 		this.pressureCD = pressureCD;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getPressureCD() {
@@ -221,7 +222,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setBaseCD(double baseCD) {
 		this.baseCD = baseCD;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getBaseCD() {
@@ -236,7 +237,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setFrictionCD(double frictionCD) {
 		this.frictionCD = frictionCD;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getFrictionCD() {
@@ -251,7 +252,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setOverrideCD(double overrideCD) {
 		this.overrideCD = overrideCD;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getOverrideCD() {
@@ -266,7 +267,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setPitchDampingMoment(double pitchDampingMoment) {
 		this.pitchDampingMoment = pitchDampingMoment;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getPitchDampingMoment() {
@@ -275,7 +276,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 
 	public void setYawDampingMoment(double yawDampingMoment) {
 		this.yawDampingMoment = yawDampingMoment;
-		modID++;
+		modID = new ModID();
 	}
 
 	public double getYawDampingMoment() {
@@ -404,7 +405,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	@Override
-	public int getModID() {
+	public ModID getModID() {
 		return modID;
 	}
 
@@ -419,7 +420,7 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 		this.CrollDamp = CrollDamp + other.getCrollDamp();
 		this.CrollForce = CrollForce + other.getCrollForce();
 
-		modID++;
+		modID = new ModID();
 
 		return this;
 	}
diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/BarrowmanCalculator.java b/core/src/main/java/info/openrocket/core/aerodynamics/BarrowmanCalculator.java
index 2a62b9c1e..94f74ac2c 100644
--- a/core/src/main/java/info/openrocket/core/aerodynamics/BarrowmanCalculator.java
+++ b/core/src/main/java/info/openrocket/core/aerodynamics/BarrowmanCalculator.java
@@ -28,6 +28,7 @@ import info.openrocket.core.rocketcomponent.SymmetricComponent;
 import info.openrocket.core.unit.UnitGroup;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.PolyInterpolator;
 import info.openrocket.core.util.Reflection;
 
@@ -1030,9 +1031,9 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
 	}
 	
 	@Override
-	public int getModID() {
+	public ModID getModID() {
 		// Only cached data is stored, return constant mod ID
-		return 0;
+		return ModID.ZERO;
 	}
 	
 }
diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java
index 4b368a997..f71fecb12 100644
--- a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java
+++ b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java
@@ -13,7 +13,7 @@ import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
 import info.openrocket.core.util.Monitorable;
 import info.openrocket.core.util.StateChangeListener;
-import info.openrocket.core.util.UniqueID;
+import info.openrocket.core.util.ModID;
 
 /**
  * A class defining the momentary flight conditions of a rocket, including
@@ -69,8 +69,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 
 	private AtmosphericConditions atmosphericConditions = new AtmosphericConditions();
 
-	private int modID;
-	private int modIDadd = 0;
+	private ModID modID;
 
 	/**
 	 * Sole constructor. The reference length is initialized to the reference length
@@ -83,7 +82,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 	public FlightConditions(FlightConfiguration config) {
 		if (config != null)
 			setRefLength(config.getReferenceLength());
-		this.modID = UniqueID.next();
+		this.modID = new ModID();
 	}
 
 	/**
@@ -370,21 +369,19 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 	public void setAtmosphericConditions(AtmosphericConditions cond) {
 		if (atmosphericConditions.equals(cond))
 			return;
-		modIDadd += atmosphericConditions.getModID();
+		modID = new ModID();
 		atmosphericConditions = cond;
 		fireChangeEvent();
 	}
 
 	/**
-	 * Retrieve the modification count of this object. Each time it is modified
-	 * the modification count is increased by one.
+	 * Retrieve the modification count of this object.
 	 * 
-	 * @return the number of times this object has been modified since
-	 *         instantiation.
+	 * @return modification ID
 	 */
 	@Override
-	public int getModID() {
-		return modID + modIDadd + this.atmosphericConditions.getModID();
+	public ModID getModID() {
+		return modID;
 	}
 
 	@Override
@@ -459,7 +456,6 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 	 * wake up call to listeners
 	 */
 	protected void fireChangeEvent() {
-		modID = UniqueID.next();
 		// Copy the list before iterating to prevent concurrent modification exceptions.
 		EventListener[] listeners = listenerList.toArray(new EventListener[0]);
 		for (EventListener l : listeners) {
diff --git a/core/src/main/java/info/openrocket/core/document/OpenRocketDocument.java b/core/src/main/java/info/openrocket/core/document/OpenRocketDocument.java
index cc9814243..c71fb737b 100644
--- a/core/src/main/java/info/openrocket/core/document/OpenRocketDocument.java
+++ b/core/src/main/java/info/openrocket/core/document/OpenRocketDocument.java
@@ -3,10 +3,6 @@ package info.openrocket.core.document;
 import java.io.File;
 import java.util.*;
 
-import info.openrocket.core.file.wavefrontobj.export.OBJExportOptions;
-import info.openrocket.core.rocketcomponent.*;
-import info.openrocket.core.startup.Preferences;
-import info.openrocket.core.util.StateChangeListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -16,12 +12,23 @@ import info.openrocket.core.appearance.DecalImage;
 import info.openrocket.core.document.events.DocumentChangeEvent;
 import info.openrocket.core.document.events.DocumentChangeListener;
 import info.openrocket.core.document.events.SimulationChangeEvent;
+import info.openrocket.core.file.wavefrontobj.export.OBJExportOptions;
 import info.openrocket.core.logging.Markers;
+import info.openrocket.core.rocketcomponent.ComponentChangeEvent;
+import info.openrocket.core.rocketcomponent.ComponentChangeListener;
+import info.openrocket.core.rocketcomponent.FlightConfiguration;
+import info.openrocket.core.rocketcomponent.FlightConfigurationId;
+import info.openrocket.core.rocketcomponent.InsideColorComponent;
+import info.openrocket.core.rocketcomponent.Rocket;
+import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.simulation.FlightDataType;
 import info.openrocket.core.simulation.customexpression.CustomExpression;
 import info.openrocket.core.simulation.extension.SimulationExtension;
 import info.openrocket.core.startup.Application;
+import info.openrocket.core.startup.Preferences;
 import info.openrocket.core.util.ArrayList;
+import info.openrocket.core.util.ModID;
+import info.openrocket.core.util.StateChangeListener;
 
 /**
  * Class describing an entire OpenRocket document, including a rocket and
@@ -90,8 +97,8 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL
 	private final ArrayList<UndoRedoListener> undoRedoListeners = new ArrayList<UndoRedoListener>(2);
 	
 	private File file = null;
-	private int modID = -1;
-	private int savedID = -1;
+	private ModID modID = ModID.INVALID;
+	private ModID savedID = ModID.INVALID;
 	
 	private final StorageOptions storageOptions = new StorageOptions();
 	private final OBJExportOptions objOptions;
@@ -232,7 +239,7 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL
 	 * @return	if the current rocket is saved
 	 */
 	public boolean isSaved() {
-		return rocket.getModID() + modID == savedID;
+		return modID == savedID;
 	}
 	
 	/**
@@ -241,9 +248,9 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL
 	 */
 	public void setSaved(boolean saved) {
 		if (!saved)
-			this.savedID = -1;
+			this.savedID = ModID.INVALID;
 		else
-			this.savedID = rocket.getModID() + modID;
+			this.savedID = modID;
 	}
 	
 	/**
@@ -645,7 +652,7 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL
 
 	@Override
 	public void stateChanged(EventObject e) {
-		modID++;
+		modID = new ModID();
 		fireDocumentChangeEvent(new DocumentChangeEvent(e.getSource()));
 	}
 
diff --git a/core/src/main/java/info/openrocket/core/document/Simulation.java b/core/src/main/java/info/openrocket/core/document/Simulation.java
index 3becacabe..9ae81a92c 100644
--- a/core/src/main/java/info/openrocket/core/document/Simulation.java
+++ b/core/src/main/java/info/openrocket/core/document/Simulation.java
@@ -32,6 +32,7 @@ import info.openrocket.core.startup.Application;
 import info.openrocket.core.util.ArrayList;
 import info.openrocket.core.util.BugException;
 import info.openrocket.core.util.ChangeSource;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.SafetyMutex;
 import info.openrocket.core.util.StateChangeListener;
 
@@ -100,7 +101,7 @@ public class Simulation implements ChangeSource, Cloneable {
 	private SimulationOptions simulatedConditions = null;
 	private String simulatedConfigurationDescription = null;
 	private FlightData simulatedData = null;
-	private int simulatedConfigurationID = -1;
+	private ModID simulatedConfigurationModID = ModID.INVALID;
 
 	/**
 	 * Create a new simulation for the rocket. Parent document should also be provided.
@@ -158,7 +159,7 @@ public class Simulation implements ChangeSource, Cloneable {
 
 		final FlightConfiguration config = rocket.getSelectedConfiguration();
 		this.setFlightConfigurationId(config.getFlightConfigurationID());
-		this.simulatedConfigurationID = config.getModID();
+		this.simulatedConfigurationModID = config.getModID();
 
 		this.simulationExtensions.addAll(extensions);
 	}
@@ -330,7 +331,7 @@ public class Simulation implements ChangeSource, Cloneable {
 		final FlightConfiguration config = rocket.getFlightConfiguration(this.getId()).clone();
 
 		if (isStatusUpToDate(status)) {
-			if (config.getModID() != simulatedConfigurationID || !options.equals(simulatedConditions)) {
+			if (config.getModID() != simulatedConfigurationModID || !options.equals(simulatedConditions)) {
 				status = Status.OUTDATED;
 			}
 		}
@@ -385,7 +386,7 @@ public class Simulation implements ChangeSource, Cloneable {
 	 * Syncs the modID with its flight configuration.
 	 */
 	public void syncModID() {
-		this.simulatedConfigurationID = getActiveConfiguration().getModID();
+		this.simulatedConfigurationModID = getActiveConfiguration().getModID();
 		fireChangeEvent();
 	}
 	
@@ -441,7 +442,7 @@ public class Simulation implements ChangeSource, Cloneable {
 			// Set simulated info after simulation
 			simulatedConditions = options.clone();
 			simulatedConfigurationDescription = descriptor.format(this.rocket, getId());
-			simulatedConfigurationID = getActiveConfiguration().getModID();
+			simulatedConfigurationModID = getActiveConfiguration().getModID();
 			if (simulator != null) {
 				simulatedData = simulator.getFlightData();
 			}
@@ -554,7 +555,7 @@ public class Simulation implements ChangeSource, Cloneable {
 			copy.simulatedConditions = null;
 			copy.simulatedConfigurationDescription = null;
 			copy.simulatedData = null;
-			copy.simulatedConfigurationID = -1;
+			copy.simulatedConfigurationModID = ModID.INVALID;
 			
 			return copy;
 			
@@ -574,7 +575,7 @@ public class Simulation implements ChangeSource, Cloneable {
 			clone.name = this.name;
 			clone.configId = this.configId;
 			clone.simulatedConfigurationDescription = this.simulatedConfigurationDescription;
-			clone.simulatedConfigurationID = this.simulatedConfigurationID;
+			clone.simulatedConfigurationModID = this.simulatedConfigurationModID;
 			clone.options = this.options.clone();
 			clone.listeners = new ArrayList<>();
 			if (this.simulatedConditions != null) {
@@ -609,7 +610,7 @@ public class Simulation implements ChangeSource, Cloneable {
 			this.name = simulation.name;
 			this.configId = simulation.configId;
 			this.simulatedConfigurationDescription = simulation.simulatedConfigurationDescription;
-			this.simulatedConfigurationID = simulation.simulatedConfigurationID;
+			this.simulatedConfigurationModID = simulation.simulatedConfigurationModID;
 			this.options.copyConditionsFrom(simulation.options);
 			if (simulation.simulatedConditions == null) {
 				this.simulatedConditions = null;
diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java
index b5c5f294f..7ba4088b8 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java
@@ -1,6 +1,7 @@
 package info.openrocket.core.file.openrocket.importt;
 
 import java.util.HashMap;
+import java.util.UUID;
 
 import info.openrocket.core.logging.SimulationAbort;
 import info.openrocket.core.logging.SimulationAbort.Cause;
@@ -153,7 +154,7 @@ class FlightDataBranchHandler extends AbstractElementHandler {
 			Rocket rocket = context.getOpenRocketDocument().getRocket();
 			sourceID = attributes.get("source");
 			if (sourceID != null) {
-				source = rocket.findComponent(sourceID);
+				source = rocket.findComponent(UUID.fromString(sourceID));
 			}
 
 			// For aborts, get the cause
diff --git a/core/src/main/java/info/openrocket/core/logging/MessageSet.java b/core/src/main/java/info/openrocket/core/logging/MessageSet.java
index d2bacfa91..20aa0fd03 100644
--- a/core/src/main/java/info/openrocket/core/logging/MessageSet.java
+++ b/core/src/main/java/info/openrocket/core/logging/MessageSet.java
@@ -3,6 +3,7 @@ package info.openrocket.core.logging;
 import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.util.ArrayList;
 import info.openrocket.core.util.BugException;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Monitorable;
 import info.openrocket.core.util.Mutable;
 
@@ -25,7 +26,7 @@ public abstract class MessageSet<E extends Message> extends AbstractSet<E> imple
     protected ArrayList<E> messages = new ArrayList<>();
 
     protected Mutable mutable = new Mutable();
-    private int modID = 0;
+    private ModID modID = ModID.ZERO;
 
     /**
      * Add a <code>Message</code> to the set.  If a message of the same type
@@ -38,7 +39,7 @@ public abstract class MessageSet<E extends Message> extends AbstractSet<E> imple
     public boolean add(E m) {
         mutable.check();
 
-        modID++;
+        modID = new ModID();
         int index = messages.indexOf(m);
 
         if (index < 0) {
@@ -166,7 +167,7 @@ public abstract class MessageSet<E extends Message> extends AbstractSet<E> imple
     }
 
     @Override
-    public int getModID() {
+    public ModID getModID() {
         return modID;
     }
 }
diff --git a/core/src/main/java/info/openrocket/core/masscalc/MassCalculator.java b/core/src/main/java/info/openrocket/core/masscalc/MassCalculator.java
index 852a18e55..5dd40a0c0 100644
--- a/core/src/main/java/info/openrocket/core/masscalc/MassCalculator.java
+++ b/core/src/main/java/info/openrocket/core/masscalc/MassCalculator.java
@@ -9,8 +9,10 @@ import info.openrocket.core.rocketcomponent.FlightConfiguration;
 import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.simulation.MotorClusterState;
 import info.openrocket.core.simulation.SimulationStatus;
-import info.openrocket.core.util.*;
-
+import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
+import info.openrocket.core.util.Monitorable;
+import info.openrocket.core.util.Transformation;
 public class MassCalculator implements Monitorable {
 
 	public static final double MIN_MASS = MathUtil.EPSILON;
@@ -24,7 +26,7 @@ public class MassCalculator implements Monitorable {
 	// private MassData rocketSpentMassCache;
 	// private MassData motorMassCache;
 
-	private final int modId = 0;
+	private final ModID modID = ModID.ZERO;
 
 	////////////////// Constructors ///////////////////
 	public MassCalculator() {
@@ -176,8 +178,8 @@ public class MassCalculator implements Monitorable {
 
 	////////////////// Mass property calculations ///////////////////
 	@Override
-	public int getModID() {
-		return this.modId;
+	public ModID getModID() {
+		return modID;
 	}
 
 }
diff --git a/core/src/main/java/info/openrocket/core/models/atmosphere/AtmosphericConditions.java b/core/src/main/java/info/openrocket/core/models/atmosphere/AtmosphericConditions.java
index 6e9fd8f0a..35644a89b 100644
--- a/core/src/main/java/info/openrocket/core/models/atmosphere/AtmosphericConditions.java
+++ b/core/src/main/java/info/openrocket/core/models/atmosphere/AtmosphericConditions.java
@@ -3,7 +3,7 @@ package info.openrocket.core.models.atmosphere;
 import info.openrocket.core.util.BugException;
 import info.openrocket.core.util.MathUtil;
 import info.openrocket.core.util.Monitorable;
-import info.openrocket.core.util.UniqueID;
+import info.openrocket.core.util.ModID;
 
 public class AtmosphericConditions implements Cloneable, Monitorable {
 
@@ -25,7 +25,7 @@ public class AtmosphericConditions implements Cloneable, Monitorable {
 	/** Air temperature, in Kelvins. */
 	private double temperature;
 
-	private int modID;
+	private ModID modID;
 
 	/**
 	 * Construct standard atmospheric conditions.
@@ -43,7 +43,7 @@ public class AtmosphericConditions implements Cloneable, Monitorable {
 	public AtmosphericConditions(double temperature, double pressure) {
 		this.setTemperature(temperature);
 		this.setPressure(pressure);
-		this.modID = UniqueID.next();
+		this.modID = new ModID();
 	}
 
 	public double getPressure() {
@@ -52,7 +52,7 @@ public class AtmosphericConditions implements Cloneable, Monitorable {
 
 	public void setPressure(double pressure) {
 		this.pressure = pressure;
-		this.modID = UniqueID.next();
+		this.modID = new ModID();
 	}
 
 	public double getTemperature() {
@@ -61,7 +61,7 @@ public class AtmosphericConditions implements Cloneable, Monitorable {
 
 	public void setTemperature(double temperature) {
 		this.temperature = temperature;
-		this.modID = UniqueID.next();
+		this.modID = new ModID();
 	}
 
 	/**
@@ -133,7 +133,7 @@ public class AtmosphericConditions implements Cloneable, Monitorable {
 	}
 
 	@Override
-	public int getModID() {
+	public ModID getModID() {
 		return modID;
 	}
 
diff --git a/core/src/main/java/info/openrocket/core/models/atmosphere/ExtendedISAModel.java b/core/src/main/java/info/openrocket/core/models/atmosphere/ExtendedISAModel.java
index b236669b8..1924d75c8 100644
--- a/core/src/main/java/info/openrocket/core/models/atmosphere/ExtendedISAModel.java
+++ b/core/src/main/java/info/openrocket/core/models/atmosphere/ExtendedISAModel.java
@@ -2,6 +2,7 @@ package info.openrocket.core.models.atmosphere;
 
 import static info.openrocket.core.models.atmosphere.AtmosphericConditions.R;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 
 /**
  * An atmospheric temperature/pressure model based on the International Standard
@@ -122,8 +123,8 @@ public class ExtendedISAModel extends InterpolatingAtmosphericModel {
 	}
 
 	@Override
-	public int getModID() {
-		return 0;
+	public ModID getModID() {
+		return ModID.ZERO;
 	}
 
 }
diff --git a/core/src/main/java/info/openrocket/core/models/gravity/WGSGravityModel.java b/core/src/main/java/info/openrocket/core/models/gravity/WGSGravityModel.java
index 72e1d3fca..ac7cbc95e 100644
--- a/core/src/main/java/info/openrocket/core/models/gravity/WGSGravityModel.java
+++ b/core/src/main/java/info/openrocket/core/models/gravity/WGSGravityModel.java
@@ -1,6 +1,7 @@
 package info.openrocket.core.models.gravity;
 
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.WorldCoordinate;
 
 /**
@@ -28,9 +29,9 @@ public class WGSGravityModel implements GravityModel {
 	}
 
 	@Override
-	public int getModID() {
+	public ModID getModID() {
 		// The model is immutable, so it can return a constant mod ID
-		return 0;
+		return ModID.ZERO;
 	}
 
 	private double calcGravity(WorldCoordinate wc) {
diff --git a/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java b/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
index 735c48836..85a435259 100644
--- a/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
+++ b/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
@@ -4,6 +4,7 @@ import java.util.Random;
 
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.PinkNoise;
 
 /**
@@ -161,8 +162,8 @@ public class PinkNoiseWindModel implements WindModel {
 	}
 
 	@Override
-	public int getModID() {
-		return (int) (average * 1000 + standardDeviation);
+	public ModID getModID() {
+		return ModID.ZERO; // I'll bet this is wrong...
 	}
 
 }
diff --git a/core/src/main/java/info/openrocket/core/motor/MotorConfiguration.java b/core/src/main/java/info/openrocket/core/motor/MotorConfiguration.java
index 104ad6a05..eab81f865 100644
--- a/core/src/main/java/info/openrocket/core/motor/MotorConfiguration.java
+++ b/core/src/main/java/info/openrocket/core/motor/MotorConfiguration.java
@@ -9,6 +9,7 @@ import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.startup.Application;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.Inertia;
+import info.openrocket.core.util.ModID;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -34,7 +35,7 @@ public class MotorConfiguration implements FlightConfigurableParameter<MotorConf
 
 	private final List<MotorConfiguration> configListeners = new LinkedList<>();
 
-	private int modID = 0;
+	private ModID modID = ModID.INVALID;
 
 	public MotorConfiguration(final MotorMount _mount, final FlightConfigurationId _fcid) {
 		if (null == _mount) {
@@ -48,7 +49,7 @@ public class MotorConfiguration implements FlightConfigurableParameter<MotorConf
 		this.fcid = _fcid;
 		this.mid = new MotorConfigurationId(_mount, _fcid);
 
-		modID++;
+		modID = new ModID();
 	}
 
 	public MotorConfiguration(final MotorMount _mount, final FlightConfigurationId _fcid,
@@ -262,7 +263,7 @@ public class MotorConfiguration implements FlightConfigurableParameter<MotorConf
 		this.ignitionEvent = configuration.ignitionEvent;
 	}
 
-	public int getModID() {
+	public ModID getModID() {
 		return modID;
 	}
 
diff --git a/core/src/main/java/info/openrocket/core/optimization/rocketoptimization/modifiers/FlightConfigurationModifier.java b/core/src/main/java/info/openrocket/core/optimization/rocketoptimization/modifiers/FlightConfigurationModifier.java
index 971e86252..637369315 100644
--- a/core/src/main/java/info/openrocket/core/optimization/rocketoptimization/modifiers/FlightConfigurationModifier.java
+++ b/core/src/main/java/info/openrocket/core/optimization/rocketoptimization/modifiers/FlightConfigurationModifier.java
@@ -1,6 +1,7 @@
 package info.openrocket.core.optimization.rocketoptimization.modifiers;
 
 import java.util.Locale;
+import java.util.UUID;
 
 import info.openrocket.core.document.Simulation;
 import info.openrocket.core.optimization.general.OptimizationException;
@@ -14,7 +15,7 @@ import info.openrocket.core.util.Reflection.Method;
 public class FlightConfigurationModifier<E extends FlightConfigurableParameter<E>> extends GenericModifier<E> {
 
 	private final Class<? extends RocketComponent> componentClass;
-	private final String componentId;
+	private final UUID componentId;
 	private final Method configGetter;
 
 	/**
@@ -47,7 +48,7 @@ public class FlightConfigurationModifier<E extends FlightConfigurableParameter<E
 			UnitGroup unitGroup,
 			double multiplier,
 			Class<? extends RocketComponent> componentClass,
-			String componentId,
+			UUID componentId,
 			String configName,
 			Class<E> flightConfigClass,
 			String methodName) {
diff --git a/core/src/main/java/info/openrocket/core/optimization/rocketoptimization/modifiers/GenericComponentModifier.java b/core/src/main/java/info/openrocket/core/optimization/rocketoptimization/modifiers/GenericComponentModifier.java
index 473207003..d37febfbf 100644
--- a/core/src/main/java/info/openrocket/core/optimization/rocketoptimization/modifiers/GenericComponentModifier.java
+++ b/core/src/main/java/info/openrocket/core/optimization/rocketoptimization/modifiers/GenericComponentModifier.java
@@ -1,5 +1,7 @@
 package info.openrocket.core.optimization.rocketoptimization.modifiers;
 
+import java.util.UUID;
+
 import info.openrocket.core.document.Simulation;
 import info.openrocket.core.optimization.general.OptimizationException;
 import info.openrocket.core.rocketcomponent.RocketComponent;
@@ -15,7 +17,7 @@ import info.openrocket.core.unit.UnitGroup;
 public class GenericComponentModifier extends GenericModifier<RocketComponent> {
 
 	private final Class<? extends RocketComponent> componentClass;
-	private final String componentId;
+	private final UUID componentId;
 
 	/**
 	 * Sole constructor.
@@ -39,7 +41,7 @@ public class GenericComponentModifier extends GenericModifier<RocketComponent> {
 	 */
 	public GenericComponentModifier(String modifierName, String modifierDescription, Object relatedObject,
 			UnitGroup unitGroup,
-			double multiplier, Class<? extends RocketComponent> componentClass, String componentId, String methodName) {
+			double multiplier, Class<? extends RocketComponent> componentClass, UUID componentId, String methodName) {
 		super(modifierName, modifierDescription, relatedObject, unitGroup, multiplier, componentClass, methodName);
 
 		this.componentClass = componentClass;
diff --git a/core/src/main/java/info/openrocket/core/rocketcomponent/BodyTube.java b/core/src/main/java/info/openrocket/core/rocketcomponent/BodyTube.java
index 29ec7e348..3e13f48b1 100644
--- a/core/src/main/java/info/openrocket/core/rocketcomponent/BodyTube.java
+++ b/core/src/main/java/info/openrocket/core/rocketcomponent/BodyTube.java
@@ -8,7 +8,11 @@ import info.openrocket.core.motor.MotorConfiguration;
 import info.openrocket.core.motor.MotorConfigurationSet;
 import info.openrocket.core.preset.ComponentPreset;
 import info.openrocket.core.startup.Application;
-import info.openrocket.core.util.*;
+import info.openrocket.core.util.BoundingBox;
+import info.openrocket.core.util.BugException;
+import info.openrocket.core.util.Coordinate;
+import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 
 /**
  * Rocket body tube component. Has only two parameters, a radius and length.
diff --git a/core/src/main/java/info/openrocket/core/rocketcomponent/FlightConfiguration.java b/core/src/main/java/info/openrocket/core/rocketcomponent/FlightConfiguration.java
index 21a975f9a..72b0fef3e 100644
--- a/core/src/main/java/info/openrocket/core/rocketcomponent/FlightConfiguration.java
+++ b/core/src/main/java/info/openrocket/core/rocketcomponent/FlightConfiguration.java
@@ -6,6 +6,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 import info.openrocket.core.formatting.RocketDescriptor;
@@ -20,6 +21,7 @@ import info.openrocket.core.util.ArrayList;
 import info.openrocket.core.util.BoundingBox;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Monitorable;
 import info.openrocket.core.util.Transformation;
 
@@ -47,9 +49,9 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 	private class StageFlags implements Cloneable {
 		public boolean active = true;
 		public int stageNumber = -1;
-		public String stageId;
+		public UUID stageId;
 
-		public StageFlags(int _num, String stageId, boolean _active) {
+		public StageFlags(int _num, UUID stageId, boolean _active) {
 			this.stageNumber = _num;
 			this.stageId = stageId;
 			this.active = _active;
@@ -71,16 +73,16 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 	final private InstanceMap extraRenderInstances = new InstanceMap(); // Extra instances to be rendered, besides the
 																		// active instances
 
-	private int boundsModID = -1;
+	private ModID boundsModID = ModID.INVALID;
 	private BoundingBox cachedBoundsAerodynamic = new BoundingBox(); // Bounding box of all aerodynamic components
 	private BoundingBox cachedBounds = new BoundingBox(); // Bounding box of all components
 	private double cachedLengthAerodynamic = -1; // Rocket length of all aerodynamic components
 	private double cachedLength = -1; // Rocket length of all components
 
-	private int refLengthModID = -1;
+	private ModID refLengthModID = ModID.INVALID;
 	private double cachedRefLength = -1;
 
-	private int modID = 0;
+	private ModID modID = ModID.ZERO;
 
 	/**
 	 * Create a Default configuration with the specified <code>Rocket</code>.
@@ -542,9 +544,9 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 
 	// for outgoing events only
 	protected void fireChangeEvent() {
-		this.modID++;
-		boundsModID = -1;
-		refLengthModID = -1;
+		this.modID = new ModID();
+		boundsModID = ModID.INVALID;
+		refLengthModID = ModID.INVALID;
 
 		updateStages();
 		updateMotors();
@@ -555,7 +557,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 	 * Update the configuration's modID, thus staging it in need to update.
 	 */
 	public void updateModID() {
-		this.modID++;
+		modID = new ModID();
 	}
 
 	private void updateStages() {
@@ -627,7 +629,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 
 		this.motors.put(motorConfig.getID(), motorConfig);
 
-		modID++;
+		modID = new ModID();
 	}
 
 	public boolean hasMotors() {
@@ -870,8 +872,8 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 		clone.cachedBoundsAerodynamic = this.cachedBoundsAerodynamic.clone();
 		clone.cachedBounds = this.cachedBounds.clone();
 		clone.modID = this.modID;
-		clone.boundsModID = -1;
-		clone.refLengthModID = -1;
+		clone.boundsModID = ModID.INVALID;
+		clone.refLengthModID = ModID.INVALID;
 		return clone;
 	}
 
@@ -906,14 +908,14 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 		copy.cachedBoundsAerodynamic = this.cachedBoundsAerodynamic.clone();
 		copy.cachedBounds = this.cachedBounds.clone();
 		copy.modID = this.modID;
-		copy.boundsModID = -1;
-		copy.refLengthModID = -1;
+		copy.boundsModID = ModID.INVALID;
+		copy.refLengthModID = ModID.INVALID;
 		copy.configurationName = configurationName;
 		return copy;
 	}
 
 	@Override
-	public int getModID() {
+	public ModID getModID() {
 		return modID;
 	}
 
@@ -952,7 +954,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
 		final String fmt = "    [%-2s][%4s]: %6s \n";
 		buf.append(String.format(fmt, "#", "?actv", "Name"));
 		for (StageFlags flags : stages.values()) {
-			final String stageId = flags.stageId;
+			final UUID stageId = flags.stageId;
 			buf.append(String.format(fmt, stageId, (flags.active ? " on" : "off"), rocket.getStage(stageId).getName()));
 		}
 		buf.append("\n");
diff --git a/core/src/main/java/info/openrocket/core/rocketcomponent/InnerTube.java b/core/src/main/java/info/openrocket/core/rocketcomponent/InnerTube.java
index 2e188604a..ace83e476 100644
--- a/core/src/main/java/info/openrocket/core/rocketcomponent/InnerTube.java
+++ b/core/src/main/java/info/openrocket/core/rocketcomponent/InnerTube.java
@@ -13,11 +13,13 @@ import info.openrocket.core.motor.Motor;
 import info.openrocket.core.motor.MotorConfiguration;
 import info.openrocket.core.motor.MotorConfigurationSet;
 import info.openrocket.core.preset.ComponentPreset;
+import info.openrocket.core.rocketcomponent.ThicknessRingComponent;
 import info.openrocket.core.rocketcomponent.position.AxialPositionable;
 import info.openrocket.core.startup.Application;
 import info.openrocket.core.util.BugException;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 
 /**
  * This class defines an inner tube that can be used as a motor mount. The
diff --git a/core/src/main/java/info/openrocket/core/rocketcomponent/MotorMount.java b/core/src/main/java/info/openrocket/core/rocketcomponent/MotorMount.java
index 785700512..48492a13e 100644
--- a/core/src/main/java/info/openrocket/core/rocketcomponent/MotorMount.java
+++ b/core/src/main/java/info/openrocket/core/rocketcomponent/MotorMount.java
@@ -1,6 +1,7 @@
 package info.openrocket.core.rocketcomponent;
 
 import java.util.Iterator;
+import java.util.UUID;
 
 import info.openrocket.core.motor.MotorConfiguration;
 import info.openrocket.core.motor.MotorConfigurationSet;
@@ -74,7 +75,7 @@ public interface MotorMount extends ChangeSource, FlightConfigurableComponent {
 	public double getLength();
 
 	// duplicate of RocketComponent
-	public String getID();
+	public UUID getID();
 
 	// duplicate of RocketComponent
 	public String getDebugName();
diff --git a/core/src/main/java/info/openrocket/core/rocketcomponent/Rocket.java b/core/src/main/java/info/openrocket/core/rocketcomponent/Rocket.java
index 867b6ea6d..577dc7714 100644
--- a/core/src/main/java/info/openrocket/core/rocketcomponent/Rocket.java
+++ b/core/src/main/java/info/openrocket/core/rocketcomponent/Rocket.java
@@ -12,8 +12,9 @@ import info.openrocket.core.startup.Application;
 import info.openrocket.core.util.BoundingBox;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.StateChangeListener;
-import info.openrocket.core.util.UniqueID;
+import info.openrocket.core.util.ModID;
 
 /**
  * Base for all rocket components.  This is the "starting point" for all rocket trees.
@@ -43,11 +44,11 @@ public class Rocket extends ComponentAssembly {
 	private List<ComponentChangeEvent> freezeList = null;
 	
 	
-	private int modID;
-	private int massModID;
-	private int aeroModID;
-	private int treeModID;
-	private int functionalModID;
+	private ModID modID;
+	private ModID massModID;
+	private ModID aeroModID;
+	private ModID treeModID;
+	private ModID functionalModID;
 
 	private boolean eventsEnabled = false;
 
@@ -72,7 +73,7 @@ public class Rocket extends ComponentAssembly {
 	
 	public Rocket() {
 		super(AxialMethod.ABSOLUTE);
-		modID = UniqueID.next();
+		modID = new ModID();
 		massModID = modID;
 		aeroModID = modID;
 		treeModID = modID;
@@ -148,37 +149,37 @@ public class Rocket extends ComponentAssembly {
 	 *
 	 * @return   a unique ID number for this modification state.
 	 */
-	public int getModID() {
+	public ModID getModID() {
 		return modID;
 	}
 	
 	/**
-	 * Return the non-negative mass modification ID of this rocket.  See
+	 * Return the mass modification ID of this rocket.  See
 	 * {@link #getModID()} for details.
 	 *
 	 * @return   a unique ID number for this mass-modification state.
 	 */
-	public int getMassModID() {
+	public ModID getMassModID() {
 		return massModID;
 	}
 	
 	/**
-	 * Return the non-negative aerodynamic modification ID of this rocket.  See
+	 * Return the aerodynamic modification ID of this rocket.  See
 	 * {@link #getModID()} for details.
 	 *
 	 * @return   a unique ID number for this aerodynamic-modification state.
 	 */
-	public int getAerodynamicModID() {
+	public ModID getAerodynamicModID() {
 		return aeroModID;
 	}
 	
 	/**
-	 * Return the non-negative tree modification ID of this rocket.  See
+	 * Return the tree modification ID of this rocket.  See
 	 * {@link #getModID()} for details.
 	 *
 	 * @return   a unique ID number for this tree-modification state.
 	 */
-	public int getTreeModID() {
+	public ModID getTreeModID() {
 		return treeModID;
 	}
 	
@@ -188,7 +189,7 @@ public class Rocket extends ComponentAssembly {
 	 *
 	 * @return	a unique ID number for this functional modification state.
 	 */
-	public int getFunctionalModID() {
+	public ModID getFunctionalModID() {
 		return functionalModID;
 	}
 	
@@ -200,7 +201,7 @@ public class Rocket extends ComponentAssembly {
 		return this.stageMap.get(stageNumber);
 	}
 
-	public AxialStage getStage(final String stageId) {
+	public AxialStage getStage(final UUID stageId) {
 		for (AxialStage stage : getStageList()) {
 			if (stage.getID().equals(stageId)) {
 				return stage;
@@ -547,7 +548,7 @@ public class Rocket extends ComponentAssembly {
 
 			// Update modification ID's only for normal (not undo/redo) events
 			if (!cce.isUndoChange()) {
-				modID = UniqueID.next();
+				modID = new ModID();
 				if (cce.isMassChange())
 					massModID = modID;
 				if (cce.isAerodynamicChange())
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 09b72676c..cc8dd9d98 100644
--- a/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java
+++ b/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java
@@ -9,6 +9,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.UUID;
 
 import info.openrocket.core.aerodynamics.AerodynamicCalculator;
 import info.openrocket.core.aerodynamics.AerodynamicForces;
@@ -18,6 +19,7 @@ import info.openrocket.core.logging.WarningSet;
 import info.openrocket.core.rocketcomponent.position.AnglePositionable;
 import info.openrocket.core.startup.Application;
 import info.openrocket.core.startup.Preferences;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.ORColor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,7 +41,6 @@ import info.openrocket.core.util.LineStyle;
 import info.openrocket.core.util.MathUtil;
 import info.openrocket.core.util.SafetyMutex;
 import info.openrocket.core.util.StateChangeListener;
-import info.openrocket.core.util.UniqueID;
 
 /**
  * 	Master class that defines components of rockets
@@ -133,7 +134,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 	private String comment = "";
 	
 	// Unique ID of the component
-	private String id = null;
+	private UUID id = null;
 	
 	// Preset component this component is based upon
 	private ComponentPreset presetComponent = null;
@@ -1296,12 +1297,12 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 	 *
 	 * @return	the ID of the component.
 	 */
-	public final String getID() {
+	public final UUID getID() {
 		return id;
 	}
 	
 	public final String getDebugName() {
-		return (name + "/" + id.substring(0,8));
+		return (name + "/" + id.toString().substring(0,8));
 	}
 	
 	/**
@@ -1309,7 +1310,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 	 */
 	private final void newID() {
 		mutex.verify();
-		this.id = UniqueID.uuid();
+		this.id = UUID.randomUUID();
 	}
 
 	/**
@@ -1317,12 +1318,14 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 	 * Generally not recommended to directly set the ID, this is done automatically. Only use this in case you have to.
 	 * @param newID new ID
 	 */
-	public void setID(String newID) {
+	public void setID(UUID newID) {
 		mutex.verify();
 		this.id = newID;
 	}
 	
-	
+	public void setID(String newID) {
+		setID(UUID.fromString(newID));
+	}
 	/**
 	 * Get the characteristic length of the component, for example the length of a body tube
 	 * of the length of the root chord of a fin.  This is used in positioning the component
@@ -2392,7 +2395,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 	 * @param idToFind  ID to search for.
 	 * @return    The component with the ID, or null if not found.
 	 */
-	public final RocketComponent findComponent(String idToFind) {
+	public final RocketComponent findComponent(UUID idToFind) {
 		checkState();
 		mutex.lock("findComponent");
 		Iterator<RocketComponent> iter = this.iterator(true);
@@ -2960,7 +2963,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 		private final Deque<Iterator<RocketComponent>> iteratorStack = new ArrayDeque<Iterator<RocketComponent>>();
 		
 		private final Rocket root;
-		private final int treeModID;
+		private final ModID treeModID;
 		
 		private final RocketComponent original;
 		private boolean returnSelf = false;
@@ -2974,7 +2977,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 				treeModID = root.getTreeModID();
 			} else {
 				root = null;
-				treeModID = -1;
+				treeModID = ModID.INVALID;
 			}
 			
 			Iterator<RocketComponent> i = c.children.iterator();
@@ -3041,7 +3044,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
 
 	/// debug functions
 	public String toDebugName() {
-		return this.getName() + "<" + this.getClass().getSimpleName() + ">(" + this.getID().substring(0, 8) + ")";
+		return this.getName() + "<" + this.getClass().getSimpleName() + ">(" + this.getID().toString().substring(0, 8) + ")";
 	}
 	
 	// multi-line output
diff --git a/core/src/main/java/info/openrocket/core/simulation/EventQueue.java b/core/src/main/java/info/openrocket/core/simulation/EventQueue.java
index 31a3d9661..a2c2c9afd 100644
--- a/core/src/main/java/info/openrocket/core/simulation/EventQueue.java
+++ b/core/src/main/java/info/openrocket/core/simulation/EventQueue.java
@@ -2,6 +2,7 @@ package info.openrocket.core.simulation;
 
 import java.util.PriorityQueue;
 
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Monitorable;
 
 /**
@@ -13,7 +14,7 @@ import info.openrocket.core.util.Monitorable;
  */
 public class EventQueue extends PriorityQueue<FlightEvent> implements Monitorable {
 
-	private int modID = 0;
+	private ModID modID = ModID.INVALID;
 
 	public EventQueue() {
 		super();
@@ -25,36 +26,36 @@ public class EventQueue extends PriorityQueue<FlightEvent> implements Monitorabl
 
 	@Override
 	public boolean add(FlightEvent e) {
-		modID++;
+		modID = new ModID();
 		return super.add(e);
 	}
 
 	@Override
 	public void clear() {
-		modID++;
+		modID = new ModID();
 		super.clear();
 	}
 
 	@Override
 	public boolean offer(FlightEvent e) {
-		modID++;
+		modID = new ModID();
 		return super.offer(e);
 	}
 
 	@Override
 	public FlightEvent poll() {
-		modID++;
+		modID = new ModID();
 		return super.poll();
 	}
 
 	@Override
 	public boolean remove(Object o) {
-		modID++;
+		modID = new ModID();
 		return super.remove(o);
 	}
 
 	@Override
-	public int getModID() {
+	public ModID getModID() {
 		return modID;
 	}
 
diff --git a/core/src/main/java/info/openrocket/core/simulation/FlightDataBranch.java b/core/src/main/java/info/openrocket/core/simulation/FlightDataBranch.java
index 5a173e95e..3dbcec4ac 100644
--- a/core/src/main/java/info/openrocket/core/simulation/FlightDataBranch.java
+++ b/core/src/main/java/info/openrocket/core/simulation/FlightDataBranch.java
@@ -10,6 +10,7 @@ import info.openrocket.core.rocketcomponent.AxialStage;
 import info.openrocket.core.rocketcomponent.Rocket;
 import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.util.ArrayList;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Monitorable;
 import info.openrocket.core.util.Mutable;
 
@@ -51,7 +52,7 @@ public class FlightDataBranch implements Monitorable {
 	
 	private final Mutable mutable = new Mutable();
 	
-	private int modID = 0;
+	private ModID modID = ModID.INVALID;
 	
 	/**
 	 * Sole constructor.  Defines the name of the FlightDataBranch and at least one variable type.
@@ -117,7 +118,7 @@ public class FlightDataBranch implements Monitorable {
 			sanityCheckValues(type, Double.NaN);
 			values.get(type).add(Double.NaN);
 		}
-		modID++;
+		modID = new ModID();
 	}
 
 	private void sanityCheckValues(FlightDataType type, Double value) {
@@ -162,7 +163,7 @@ public class FlightDataBranch implements Monitorable {
 		if (Double.isNaN(max) || (value > max)) {
 			maxValues.put(type, value);
 		}
-		modID++;
+		modID = new ModID();
 	}
 
 	/**
@@ -381,7 +382,7 @@ public class FlightDataBranch implements Monitorable {
 	public void addEvent(FlightEvent event) {
 		mutable.check();
 		events.add(event);
-		modID++;
+		modID = new ModID();
 	}
 	
 	
@@ -441,7 +442,7 @@ public class FlightDataBranch implements Monitorable {
 	
 	
 	@Override
-	public int getModID() {
+	public ModID getModID() {
 		return modID;
 	}
 
diff --git a/core/src/main/java/info/openrocket/core/simulation/SimulationConditions.java b/core/src/main/java/info/openrocket/core/simulation/SimulationConditions.java
index 93f200161..4dc71864a 100644
--- a/core/src/main/java/info/openrocket/core/simulation/SimulationConditions.java
+++ b/core/src/main/java/info/openrocket/core/simulation/SimulationConditions.java
@@ -15,6 +15,7 @@ import info.openrocket.core.simulation.listeners.SimulationListener;
 import info.openrocket.core.util.BugException;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.GeodeticComputationStrategy;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Monitorable;
 import info.openrocket.core.util.WorldCoordinate;
 
@@ -62,8 +63,8 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	private int randomSeed = 0;
 
-	private int modID = 0;
-	private int modIDadd = 0;
+	private ModID modID = ModID.INVALID;
+	private ModID modIDadd = ModID.INVALID;
 
 	public AerodynamicCalculator getAerodynamicCalculator() {
 		return aerodynamicCalculator;
@@ -71,8 +72,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setAerodynamicCalculator(AerodynamicCalculator aerodynamicCalculator) {
 		if (this.aerodynamicCalculator != null)
-			this.modIDadd += this.aerodynamicCalculator.getModID();
-		this.modID++;
+			this.modIDadd = new ModID();
 		this.aerodynamicCalculator = aerodynamicCalculator;
 	}
 
@@ -82,8 +82,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setMassCalculator(MassCalculator massCalculator) {
 		if (this.massCalculator != null)
-			this.modIDadd += this.massCalculator.getModID();
-		this.modID++;
+			this.modIDadd = new ModID();
 		this.massCalculator = massCalculator;
 	}
 
@@ -105,7 +104,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setLaunchRodLength(double launchRodLength) {
 		this.launchRodLength = launchRodLength;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public double getLaunchRodAngle() {
@@ -114,7 +113,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setLaunchRodAngle(double launchRodAngle) {
 		this.launchRodAngle = launchRodAngle;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public double getLaunchRodDirection() {
@@ -123,7 +122,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setLaunchRodDirection(double launchRodDirection) {
 		this.launchRodDirection = launchRodDirection;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public WorldCoordinate getLaunchSite() {
@@ -134,7 +133,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 		if (this.launchSite.equals(site))
 			return;
 		this.launchSite = site;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public Coordinate getLaunchPosition() {
@@ -145,7 +144,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 		if (this.launchPosition.equals(launchPosition))
 			return;
 		this.launchPosition = launchPosition;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public Coordinate getLaunchVelocity() {
@@ -156,7 +155,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 		if (this.launchVelocity.equals(launchVelocity))
 			return;
 		this.launchVelocity = launchVelocity;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public GeodeticComputationStrategy getGeodeticComputation() {
@@ -170,7 +169,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 			throw new IllegalArgumentException("strategy cannot be null");
 		}
 		this.geodeticComputation = geodeticComputation;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public WindModel getWindModel() {
@@ -179,8 +178,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setWindModel(WindModel windModel) {
 		if (this.windModel != null)
-			this.modIDadd += this.windModel.getModID();
-		this.modID++;
+			this.modIDadd = new ModID();
 		this.windModel = windModel;
 	}
 
@@ -190,8 +188,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setAtmosphericModel(AtmosphericModel atmosphericModel) {
 		if (this.atmosphericModel != null)
-			this.modIDadd += this.atmosphericModel.getModID();
-		this.modID++;
+			this.modIDadd = new ModID();
 		this.atmosphericModel = atmosphericModel;
 	}
 
@@ -200,9 +197,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 	}
 
 	public void setGravityModel(GravityModel gravityModel) {
-		// if (this.gravityModel != null)
-		// this.modIDadd += this.gravityModel.getModID();
-		this.modID++;
+		this.modID = new ModID();
 		this.gravityModel = gravityModel;
 	}
 
@@ -212,7 +207,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setTimeStep(double timeStep) {
 		this.timeStep = timeStep;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public double getMaximumAngleStep() {
@@ -221,7 +216,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setMaximumAngleStep(double maximumAngle) {
 		this.maximumAngleStep = maximumAngle;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public int getRandomSeed() {
@@ -230,7 +225,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
 
 	public void setRandomSeed(int randomSeed) {
 		this.randomSeed = randomSeed;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public void setSimulation(Simulation sim) {
@@ -247,11 +242,8 @@ public class SimulationConditions implements Monitorable, Cloneable {
 	}
 
 	@Override
-	public int getModID() {
-		//return (modID + modIDadd + rocket.getModID() + windModel.getModID() + atmosphericModel.getModID() +
-		//		gravityModel.getModID() + aerodynamicCalculator.getModID() + massCalculator.getModID());
-		return (modID + modIDadd + simulation.getRocket().getModID() + windModel.getModID() + atmosphericModel.getModID() +
-				aerodynamicCalculator.getModID() + massCalculator.getModID());
+	public ModID getModID() {
+		return modID;
 	}
 
 	@Override
diff --git a/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java b/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java
index 953becf09..b16f43b72 100644
--- a/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java
+++ b/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java
@@ -21,6 +21,7 @@ import info.openrocket.core.simulation.listeners.SimulationListenerHelper;
 import info.openrocket.core.util.BugException;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Monitorable;
 import info.openrocket.core.util.MonitorableSet;
 import info.openrocket.core.util.Quaternion;
@@ -92,8 +93,8 @@ public class SimulationStatus implements Cloneable, Monitorable {
 	double maxAlt = Double.NEGATIVE_INFINITY;
 	double maxAltTime = 0;
 
-	private int modID = 0;
-	private int modIDadd = 0;
+	private ModID modID = ModID.INVALID;
+	private ModID modIDadd = ModID.INVALID;
 
 	public SimulationStatus(FlightConfiguration configuration, SimulationConditions simulationConditions) {
 
@@ -211,7 +212,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setSimulationTime(double time) {
 		this.time = time;
-		this.modID++;
+		this.modID = new ModID();
 	}
 
 	public double getSimulationTime() {
@@ -220,8 +221,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setConfiguration(FlightConfiguration configuration) {
 		if (this.configuration != null)
-			this.modIDadd += this.configuration.getModID();
-		this.modID++;
+			this.modIDadd = new ModID();
 		this.configuration = configuration;
 	}
 
@@ -250,8 +250,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setFlightDataBranch(FlightDataBranch flightDataBranch) {
 		if (this.flightDataBranch != null)
-			this.modIDadd += this.flightDataBranch.getModID();
-		this.modID++;
+			this.modIDadd = new ModID();
 		this.flightDataBranch = flightDataBranch;
 	}
 
@@ -261,7 +260,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setRocketPosition(Coordinate position) {
 		this.position = position;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public Coordinate getRocketPosition() {
@@ -270,7 +269,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setRocketWorldPosition(WorldCoordinate wc) {
 		this.worldPosition = wc;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public WorldCoordinate getRocketWorldPosition() {
@@ -279,7 +278,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setRocketVelocity(Coordinate velocity) {
 		this.velocity = velocity;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public Coordinate getRocketVelocity() {
@@ -288,7 +287,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setRocketAcceleration(Coordinate acceleration) {
 		this.acceleration = acceleration;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public Coordinate getRocketAcceleration() {
@@ -308,7 +307,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setRocketOrientationQuaternion(Quaternion orientation) {
 		this.orientation = orientation;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public Coordinate getRocketRotationVelocity() {
@@ -321,7 +320,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setEffectiveLaunchRodLength(double effectiveLaunchRodLength) {
 		this.effectiveLaunchRodLength = effectiveLaunchRodLength;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public double getEffectiveLaunchRodLength() {
@@ -330,7 +329,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setSimulationStartWallTime(long simulationStartWallTime) {
 		this.simulationStartWallTime = simulationStartWallTime;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public long getSimulationStartWallTime() {
@@ -339,7 +338,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setMotorIgnited(boolean motorIgnited) {
 		this.motorIgnited = motorIgnited;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public boolean isMotorIgnited() {
@@ -348,7 +347,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setLiftoff(boolean liftoff) {
 		this.liftoff = liftoff;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public boolean isLiftoff() {
@@ -357,7 +356,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setLaunchRodCleared(boolean launchRod) {
 		this.launchRodCleared = launchRod;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public boolean isLaunchRodCleared() {
@@ -366,7 +365,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setApogeeReached(boolean apogeeReached) {
 		this.apogeeReached = apogeeReached;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public boolean isApogeeReached() {
@@ -375,7 +374,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setTumbling(boolean tumbling) {
 		this.tumbling = tumbling;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public boolean isTumbling() {
@@ -384,7 +383,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setLanded(boolean landed) {
 		this.landed = landed;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public boolean isLanded() {
@@ -397,7 +396,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setMaxAlt(double maxAlt) {
 		this.maxAlt = maxAlt;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public double getMaxAltTime() {
@@ -406,7 +405,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setMaxAltTime(double maxAltTime) {
 		this.maxAltTime = maxAltTime;
-		this.modID++;
+		modID = new ModID();
 	}
 
 	public Set<RecoveryDevice> getDeployedRecoveryDevices() {
@@ -415,8 +414,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setWarnings(WarningSet warnings) {
 		if (this.warnings != null)
-			this.modIDadd += this.warnings.getModID();
-		this.modID++;
+			this.modIDadd = new ModID();
 		this.warnings = warnings;
 	}
 
@@ -430,8 +428,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
 
 	public void setSimulationConditions(SimulationConditions simulationConditions) {
 		if (this.simulationConditions != null)
-			this.modIDadd += this.simulationConditions.getModID();
-		this.modID++;
+			this.modIDadd = new ModID();
 		this.simulationConditions = simulationConditions;
 	}
 
@@ -480,10 +477,8 @@ public class SimulationStatus implements Cloneable, Monitorable {
 	}
 
 	@Override
-	public int getModID() {
-		return (modID + modIDadd + simulationConditions.getModID() + configuration.getModID() +
-				flightDataBranch.getModID() + deployedRecoveryDevices.getModID() +
-				eventQueue.getModID() + warnings.getModID());
+	public ModID getModID() {
+		return modID;
 	}
 
 	public String toEventDebug() {
diff --git a/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java b/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java
index 0b5ca9597..e328d2871 100644
--- a/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java
+++ b/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java
@@ -18,6 +18,7 @@ import info.openrocket.core.simulation.SimulationStatus;
 import info.openrocket.core.simulation.exception.SimulationException;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 
 /**
  * Helper methods for firing events to simulation listeners.
@@ -35,7 +36,7 @@ public class SimulationListenerHelper {
 	 */
 	public static void fireStartSimulation(SimulationStatus status)
 			throws SimulationException {
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			l.startSimulation(status);
@@ -50,7 +51,7 @@ public class SimulationListenerHelper {
 	 * Fire endSimulation event.
 	 */
 	public static void fireEndSimulation(SimulationStatus status, SimulationException exception) {
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			l.endSimulation(status, exception);
@@ -70,7 +71,7 @@ public class SimulationListenerHelper {
 	public static boolean firePreStep(SimulationStatus status)
 			throws SimulationException {
 		boolean b;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			b = l.preStep(status);
@@ -91,7 +92,7 @@ public class SimulationListenerHelper {
 	 */
 	public static void firePostStep(SimulationStatus status)
 			throws SimulationException {
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			l.postStep(status);
@@ -112,7 +113,7 @@ public class SimulationListenerHelper {
 	 */
 	public static boolean fireAddFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
 		boolean b;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationEventListener) {
@@ -138,7 +139,7 @@ public class SimulationListenerHelper {
 	 */
 	public static boolean fireHandleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
 		boolean b;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationEventListener) {
@@ -165,7 +166,7 @@ public class SimulationListenerHelper {
 	public static boolean fireMotorIgnition(SimulationStatus status, MotorConfigurationId motorId, MotorMount mount,
 			MotorClusterState instance) throws SimulationException {
 		boolean result;
-		int modID = status.getModID(); // Contains also motor instance
+		ModID modID = status.getModID(); // Contains also motor instance
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationEventListener) {
@@ -192,7 +193,7 @@ public class SimulationListenerHelper {
 	public static boolean fireRecoveryDeviceDeployment(SimulationStatus status, RecoveryDevice device)
 			throws SimulationException {
 		boolean result;
-		int modID = status.getModID(); // Contains also motor instance
+		ModID modID = status.getModID(); // Contains also motor instance
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationEventListener) {
@@ -220,7 +221,7 @@ public class SimulationListenerHelper {
 	public static AtmosphericConditions firePreAtmosphericModel(SimulationStatus status)
 			throws SimulationException {
 		AtmosphericConditions conditions;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -248,7 +249,7 @@ public class SimulationListenerHelper {
 			throws SimulationException {
 		AtmosphericConditions c;
 		AtmosphericConditions clone = conditions.clone();
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -275,7 +276,7 @@ public class SimulationListenerHelper {
 	public static Coordinate firePreWindModel(SimulationStatus status)
 			throws SimulationException {
 		Coordinate wind;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -300,7 +301,7 @@ public class SimulationListenerHelper {
 	 */
 	public static Coordinate firePostWindModel(SimulationStatus status, Coordinate wind) throws SimulationException {
 		Coordinate w;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -326,7 +327,7 @@ public class SimulationListenerHelper {
 	public static double firePreGravityModel(SimulationStatus status)
 			throws SimulationException {
 		double gravity;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -351,7 +352,7 @@ public class SimulationListenerHelper {
 	 */
 	public static double firePostGravityModel(SimulationStatus status, double gravity) throws SimulationException {
 		double g;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -377,7 +378,7 @@ public class SimulationListenerHelper {
 	public static FlightConditions firePreFlightConditions(SimulationStatus status)
 			throws SimulationException {
 		FlightConditions conditions;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -406,7 +407,7 @@ public class SimulationListenerHelper {
 			throws SimulationException {
 		FlightConditions c;
 		FlightConditions clone = conditions.clone();
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -433,7 +434,7 @@ public class SimulationListenerHelper {
 	public static AerodynamicForces firePreAerodynamicCalculation(SimulationStatus status)
 			throws SimulationException {
 		AerodynamicForces forces;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -460,7 +461,7 @@ public class SimulationListenerHelper {
 			throws SimulationException {
 		AerodynamicForces f;
 		AerodynamicForces clone = forces.clone();
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -487,7 +488,7 @@ public class SimulationListenerHelper {
 	public static RigidBody firePreMassCalculation(SimulationStatus status)
 			throws SimulationException {
 		RigidBody mass;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -513,7 +514,7 @@ public class SimulationListenerHelper {
 	public static RigidBody firePostMassCalculation(SimulationStatus status, RigidBody mass)
 			throws SimulationException {
 		RigidBody m;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -539,7 +540,7 @@ public class SimulationListenerHelper {
 	public static double firePreThrustCalculation(SimulationStatus status)
 			throws SimulationException {
 		double thrust;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -564,7 +565,7 @@ public class SimulationListenerHelper {
 	 */
 	public static double firePostThrustCalculation(SimulationStatus status, double thrust) throws SimulationException {
 		double t;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -589,7 +590,7 @@ public class SimulationListenerHelper {
 	 */
 	public static AccelerationData firePreAccelerationCalculation(SimulationStatus status) throws SimulationException {
 		AccelerationData acceleration;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
@@ -615,7 +616,7 @@ public class SimulationListenerHelper {
 	public static AccelerationData firePostAccelerationCalculation(SimulationStatus status,
 			AccelerationData acceleration) throws SimulationException {
 		AccelerationData a;
-		int modID = status.getModID();
+		ModID modID = status.getModID();
 
 		for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
 			if (l instanceof SimulationComputationListener) {
diff --git a/core/src/main/java/info/openrocket/core/startup/Preferences.java b/core/src/main/java/info/openrocket/core/startup/Preferences.java
index 2ea6dc0e1..c165e8c00 100644
--- a/core/src/main/java/info/openrocket/core/startup/Preferences.java
+++ b/core/src/main/java/info/openrocket/core/startup/Preferences.java
@@ -32,7 +32,6 @@ import info.openrocket.core.util.GeodeticComputationStrategy;
 import info.openrocket.core.util.LineStyle;
 import info.openrocket.core.util.MathUtil;
 import info.openrocket.core.util.StateChangeListener;
-import info.openrocket.core.util.UniqueID;
 
 public abstract class Preferences implements ChangeSource {
 	private static final String SPLIT_CHARACTER = "|";
@@ -747,20 +746,6 @@ public abstract class Preferences implements ChangeSource {
 	public void setMotorNameColumn(boolean value) {
 		putBoolean(info.openrocket.core.startup.Preferences.MOTOR_NAME_COLUMN, value);
 	}
-
-	/**
-	 * Return the OpenRocket unique ID.
-	 *
-	 * @return	a random ID string that stays constant between OpenRocket executions
-	 */
-	public final String getUniqueID() {
-		String id = this.getString("id", null);
-		if (id == null) {
-			id = UniqueID.uuid();
-			this.putString("id", id);
-		}
-		return id;
-	}
 	
 	/**
 	 * Returns a limited-range integer value from the preferences.  If the value
diff --git a/core/src/main/java/info/openrocket/core/unit/CaliberUnit.java b/core/src/main/java/info/openrocket/core/unit/CaliberUnit.java
index 21e60106b..e5a4dbd66 100644
--- a/core/src/main/java/info/openrocket/core/unit/CaliberUnit.java
+++ b/core/src/main/java/info/openrocket/core/unit/CaliberUnit.java
@@ -8,6 +8,7 @@ import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.rocketcomponent.SymmetricComponent;
 import info.openrocket.core.util.BugException;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 
 public class CaliberUnit extends GeneralUnit {
 
@@ -16,8 +17,8 @@ public class CaliberUnit extends GeneralUnit {
 	private final FlightConfiguration configuration;
 	private final Rocket rocket;
 
-	private int rocketModId = -1;
-	private int configurationModId = -1;
+	private ModID rocketModId = ModID.INVALID;
+	private ModID configurationModId = ModID.INVALID;
 
 	private double caliber = -1;
 
diff --git a/core/src/main/java/info/openrocket/core/unit/PercentageOfLengthUnit.java b/core/src/main/java/info/openrocket/core/unit/PercentageOfLengthUnit.java
index 5a1638f70..8c4ff8e82 100644
--- a/core/src/main/java/info/openrocket/core/unit/PercentageOfLengthUnit.java
+++ b/core/src/main/java/info/openrocket/core/unit/PercentageOfLengthUnit.java
@@ -3,14 +3,15 @@ package info.openrocket.core.unit;
 import info.openrocket.core.rocketcomponent.FlightConfiguration;
 import info.openrocket.core.rocketcomponent.Rocket;
 import info.openrocket.core.util.BugException;
+import info.openrocket.core.util.ModID;
 
 public class PercentageOfLengthUnit extends GeneralUnit {
 
     private final FlightConfiguration configuration;
     private final Rocket rocket;
 
-    private int rocketModId = -1;
-    private int configurationModId = -1;
+    private ModID rocketModId = ModID.INVALID;
+    private ModID configurationModId = ModID.INVALID;
 
     private double referenceLength = -1;
 
@@ -96,4 +97,4 @@ public class PercentageOfLengthUnit extends GeneralUnit {
         return getReferenceLength(rocket.getSelectedConfiguration());
     }
 
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/info/openrocket/core/util/UniqueID.java b/core/src/main/java/info/openrocket/core/util/ModID.java
similarity index 53%
rename from core/src/main/java/info/openrocket/core/util/UniqueID.java
rename to core/src/main/java/info/openrocket/core/util/ModID.java
index 78e3dda67..eb288651b 100644
--- a/core/src/main/java/info/openrocket/core/util/UniqueID.java
+++ b/core/src/main/java/info/openrocket/core/util/ModID.java
@@ -1,12 +1,11 @@
 package info.openrocket.core.util;
 
-import java.util.UUID;
 import java.util.concurrent.atomic.AtomicInteger;
 
-public class UniqueID {
+public class ModID implements Comparable {
 
 	private static final AtomicInteger nextId = new AtomicInteger(1);
-
+	private final int id;
 	/**
 	 * Return a positive integer ID unique during this program execution.
 	 * <p>
@@ -21,17 +20,26 @@ public class UniqueID {
 	 * 
 	 * @return a positive integer ID unique in this program execution.
 	 */
-	public static int next() {
-		return nextId.getAndIncrement();
+	public ModID() {
+		id = nextId.getAndIncrement();
 	}
 
-	/**
-	 * Return a new universally unique ID string.
-	 * 
-	 * @return a unique identifier string.
-	 */
-	public static String uuid() {
-		return UUID.randomUUID().toString();
+	// There are a few places in the code that want a constant or invalid ModID value; see Barrowman.java
+	private ModID(int val) {
+		id = val;
+	}
+	public static ModID ZERO = new ModID(0);
+	public static ModID INVALID = new ModID(-1);
+
+	public int toInt() {
+		return id;
+	}
+	
+	public String toString() {
+		return String.valueOf(id);
 	}
 
+	public int compareTo(Object o) {
+		return id - ((ModID) o).id;
+	}
 }
diff --git a/core/src/main/java/info/openrocket/core/util/Monitorable.java b/core/src/main/java/info/openrocket/core/util/Monitorable.java
index 5860da388..f250ece77 100644
--- a/core/src/main/java/info/openrocket/core/util/Monitorable.java
+++ b/core/src/main/java/info/openrocket/core/util/Monitorable.java
@@ -48,8 +48,8 @@ public interface Monitorable {
 	 * object.
 	 * 
 	 * @return a modification ID value for this object.
-	 * @see UniqueID#next()
+	 * @see ModID()
 	 */
-	public int getModID();
+	public ModID getModID();
 
 }
diff --git a/core/src/main/java/info/openrocket/core/util/MonitorableSet.java b/core/src/main/java/info/openrocket/core/util/MonitorableSet.java
index ab110f9db..60e46e6d2 100644
--- a/core/src/main/java/info/openrocket/core/util/MonitorableSet.java
+++ b/core/src/main/java/info/openrocket/core/util/MonitorableSet.java
@@ -4,6 +4,8 @@ import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
 
+import info.openrocket.core.util.ModID;
+
 /**
  * A Set that additionally implements the Monitorable interface.
  * 
@@ -11,23 +13,23 @@ import java.util.Iterator;
  */
 public class MonitorableSet<E> extends HashSet<E> implements Monitorable {
 
-	private int modID;
+	private ModID modID;
 
 	@Override
 	public boolean add(E e) {
-		modID++;
+		modID = new ModID();
 		return super.add(e);
 	}
 
 	@Override
 	public boolean addAll(Collection<? extends E> c) {
-		modID++;
+		modID = new ModID();
 		return super.addAll(c);
 	}
 
 	@Override
 	public void clear() {
-		modID++;
+		modID = new ModID();
 		super.clear();
 	}
 
@@ -38,24 +40,24 @@ public class MonitorableSet<E> extends HashSet<E> implements Monitorable {
 
 	@Override
 	public boolean remove(Object o) {
-		modID++;
+		modID = new ModID();
 		return super.remove(o);
 	}
 
 	@Override
 	public boolean removeAll(Collection<?> c) {
-		modID++;
+		modID = new ModID();
 		return super.removeAll(c);
 	}
 
 	@Override
 	public boolean retainAll(Collection<?> c) {
-		modID++;
+		modID = new ModID();
 		return super.retainAll(c);
 	}
 
 	@Override
-	public int getModID() {
+	public ModID getModID() {
 		return modID;
 	}
 
@@ -85,7 +87,7 @@ public class MonitorableSet<E> extends HashSet<E> implements Monitorable {
 		@Override
 		public void remove() {
 			iterator.remove();
-			modID++;
+			modID = new ModID();
 		}
 	}
 }
diff --git a/core/src/test/java/info/openrocket/core/rocketcomponent/FlightConfigurationTest.java b/core/src/test/java/info/openrocket/core/rocketcomponent/FlightConfigurationTest.java
index 721203e35..861573eb6 100644
--- a/core/src/test/java/info/openrocket/core/rocketcomponent/FlightConfigurationTest.java
+++ b/core/src/test/java/info/openrocket/core/rocketcomponent/FlightConfigurationTest.java
@@ -22,6 +22,7 @@ import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
 import info.openrocket.core.util.TestRockets;
 import info.openrocket.core.util.BaseTestCase;
+import info.openrocket.core.util.ModID;
 
 public class FlightConfigurationTest extends BaseTestCase {
 	private final static double EPSILON = MathUtil.EPSILON * 1E3;
@@ -767,14 +768,14 @@ public class FlightConfigurationTest extends BaseTestCase {
 		// Test boundModID
 		Field boundsModIDField = FlightConfiguration.class.getDeclaredField("boundsModID");
 		boundsModIDField.setAccessible(true);
-		int boundsModIDCopy = (int) boundsModIDField.get(copy);
-		assertEquals(-1, boundsModIDCopy);
+		ModID boundsModIDCopy = (ModID) boundsModIDField.get(copy);
+		assertEquals(ModID.INVALID, boundsModIDCopy);
 
 		// Test refLengthModID
 		Field refLengthModIDField = FlightConfiguration.class.getDeclaredField("refLengthModID");
 		refLengthModIDField.setAccessible(true);
-		int refLengthModIDCopy = (int) refLengthModIDField.get(copy);
-		assertEquals(-1, refLengthModIDCopy);
+		ModID refLengthModIDCopy = (ModID) refLengthModIDField.get(copy);
+		assertEquals(ModID.INVALID, refLengthModIDCopy);
 
 		// Test stageActiveness copy
 		for (int i = 0; i < original.getStageCount(); i++) {
@@ -833,14 +834,14 @@ public class FlightConfigurationTest extends BaseTestCase {
 		// Test boundModID
 		Field boundsModIDField = FlightConfiguration.class.getDeclaredField("boundsModID");
 		boundsModIDField.setAccessible(true);
-		int boundsModIDClone = (int) boundsModIDField.get(clone);
-		assertEquals(-1, boundsModIDClone);
+		ModID boundsModIDClone = (ModID) boundsModIDField.get(clone);
+		assertEquals(ModID.INVALID, boundsModIDClone);
 
 		// Test refLengthModID
 		Field refLengthModIDField = FlightConfiguration.class.getDeclaredField("refLengthModID");
 		refLengthModIDField.setAccessible(true);
-		int refLengthModIDClone = (int) refLengthModIDField.get(clone);
-		assertEquals(-1, refLengthModIDClone);
+		ModID refLengthModIDClone = (ModID) refLengthModIDField.get(clone);
+		assertEquals(ModID.INVALID, refLengthModIDClone);
 
 		// Test stageActiveness copy
 		for (int i = 0; i < original.getStageCount(); i++) {
diff --git a/core/src/test/java/info/openrocket/core/util/ModIDTest.java b/core/src/test/java/info/openrocket/core/util/ModIDTest.java
new file mode 100644
index 000000000..9a9209a28
--- /dev/null
+++ b/core/src/test/java/info/openrocket/core/util/ModIDTest.java
@@ -0,0 +1,23 @@
+package info.openrocket.core.util;
+
+import info.openrocket.core.util.ModID;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+public class ModIDTest {
+
+	@Test
+	public void modIDTest() {
+
+		ModID n1 = new ModID();
+		ModID n2 = new ModID();
+		ModID n3 = new ModID();
+		//		assertTrue(n > 0);
+		assertEquals(n2, n2);
+		assertTrue(n1.toInt() < n2.toInt());
+		assertTrue(n3.toInt() > n2.toInt());
+
+	}
+}
diff --git a/core/src/test/java/info/openrocket/core/util/UniqueIDTest.java b/core/src/test/java/info/openrocket/core/util/UniqueIDTest.java
deleted file mode 100644
index 1e2967dea..000000000
--- a/core/src/test/java/info/openrocket/core/util/UniqueIDTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package info.openrocket.core.util;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.junit.jupiter.api.Test;
-
-public class UniqueIDTest {
-
-	@Test
-	public void integerTest() {
-
-		int n = UniqueID.next();
-		assertTrue(n > 0);
-		assertEquals(n + 1, UniqueID.next());
-		assertEquals(n + 2, UniqueID.next());
-		assertEquals(n + 3, UniqueID.next());
-
-	}
-
-	@Test
-	public void stringTest() {
-		String id = UniqueID.uuid();
-		assertNotNull(id);
-		assertNotSame(id, UniqueID.uuid());
-		assertNotSame(id, UniqueID.uuid());
-	}
-
-}
diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeModel.java b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeModel.java
index faad435ce..1dc31d893 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeModel.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeModel.java
@@ -5,6 +5,7 @@ import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.UUID;
 
 import javax.swing.JTree;
 import javax.swing.event.TreeModelEvent;
@@ -102,7 +103,7 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
 
 		// Get currently expanded path IDs
 		Enumeration<TreePath> enumer = tree.getExpandedDescendants(new TreePath(path));
-		ArrayList<String> expanded = new ArrayList<String>();
+		ArrayList<UUID> expanded = new ArrayList<UUID>();
 		if (enumer != null) {
 			while (enumer.hasMoreElements()) {
 				TreePath p = enumer.nextElement();
@@ -117,7 +118,7 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
 			((TreeModelListener) l[i]).treeStructureChanged(e);
 		
 		// Re-expand the paths
-		for (String id : expanded) {
+		for (UUID id : expanded) {
 			RocketComponent c = root.findComponent(id);
 			if (c == null)
 				continue;
diff --git a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java
index 0e6ccdb44..11b7a214f 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/FinPointFigure.java
@@ -28,6 +28,7 @@ import info.openrocket.core.unit.UnitGroup;
 import info.openrocket.core.util.BoundingBox;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.StateChangeListener;
 
 
@@ -47,7 +48,7 @@ public class FinPointFigure extends AbstractScaleFigure {
 	private static final double MAJOR_TICKS = 100.0;
 
 	private final FreeformFinSet finset;
-	private int modID = -1;
+	private ModID modID = ModID.INVALID;
 
 	protected BoundingBox finBounds_m = null;
 	// Fin parent bounds
diff --git a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java
index 5437348c2..b82f28d58 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java
@@ -59,6 +59,7 @@ import info.openrocket.core.util.ChangeSource;
 import info.openrocket.core.util.Chars;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
+import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.StateChangeListener;
 
 import info.openrocket.swing.gui.components.StyledLabel;
@@ -159,7 +160,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
 	private double cpRoll = Double.NaN;
 
 	// The functional ID of the rocket that was simulated
-	private int flightDataFunctionalID = -1;
+	private ModID flightDataFunctionalID = ModID.INVALID;
     private FlightConfigurationId flightDataMotorID = null;
 
 	private SimulationWorker backgroundSimulationWorker = null;
diff --git a/swing/src/test/java/info/openrocket/swing/IntegrationTest.java b/swing/src/test/java/info/openrocket/swing/IntegrationTest.java
index e33da6c23..62ff602b7 100644
--- a/swing/src/test/java/info/openrocket/swing/IntegrationTest.java
+++ b/swing/src/test/java/info/openrocket/swing/IntegrationTest.java
@@ -10,6 +10,7 @@ import java.awt.event.ActionEvent;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.UUID;
 
 import javax.swing.Action;
 
@@ -69,7 +70,7 @@ public class IntegrationTest {
 	private AerodynamicCalculator aeroCalc = new BarrowmanCalculator();
 	private FlightConfigurationId fcid;
 	private FlightConditions conditions;
-	private String massComponentID = null;
+	private UUID massComponentID = null;
 	
 	@BeforeAll
 	public static void setUp() throws Exception {

From eb9106a6a4718a7d4afaa1526c2919b043816c49 Mon Sep 17 00:00:00 2001
From: JoePfeiffer <joseph@pfeifferfamily.net>
Date: Fri, 26 Jul 2024 09:26:50 -0600
Subject: [PATCH 2/4] Be more consistent about creating a new modID when
 changes take place

---
 .../core/aerodynamics/AerodynamicForces.java  | 58 +++++++++++++++++++
 .../core/aerodynamics/FlightConditions.java   | 19 +++++-
 2 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java b/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java
index 97fd05c04..2aff94005 100644
--- a/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java
+++ b/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java
@@ -77,7 +77,11 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setAxisymmetric(final boolean isSym) {
+		if (this.axisymmetric == isSym)
+			return;
+		
 		this.axisymmetric = isSym;
+		modID = new ModID();
 	}
 
 	/**
@@ -87,6 +91,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	 * @param component The rocket component
 	 */
 	public void setComponent(RocketComponent component) {
+		if (this.component == component)
+			return;
+		
 		this.component = component;
 		modID = new ModID();
 	}
@@ -100,6 +107,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCP(Coordinate cp) {
+		if ((this.cp != null) && this.cp.equals(cp))
+			return;
+		
 		this.cp = cp;
 		modID = new ModID();
 	}
@@ -109,6 +119,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCNa(double cNa) {
+		if (CNa == cNa)
+			return;
+		
 		CNa = cNa;
 		modID = new ModID();
 	}
@@ -118,6 +131,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCN(double cN) {
+		if (CN == cN)
+			return;
+		
 		CN = cN;
 		modID = new ModID();
 	}
@@ -127,6 +143,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCm(double cm) {
+		if (Cm == cm)
+			return;
+		
 		Cm = cm;
 		modID = new ModID();
 	}
@@ -136,6 +155,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCside(double cside) {
+		if (Cside == cside)
+			return;
+		
 		Cside = cside;
 		modID = new ModID();
 	}
@@ -145,6 +167,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCyaw(double cyaw) {
+		if (Cyaw == cyaw)
+			return;
+		
 		Cyaw = cyaw;
 		modID = new ModID();
 	}
@@ -154,6 +179,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCroll(double croll) {
+		if (Croll == croll)
+			return;
+		
 		Croll = croll;
 		modID = new ModID();
 	}
@@ -163,6 +191,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCrollDamp(double crollDamp) {
+		if (CrollDamp == crollDamp)
+			return;
+		
 		CrollDamp = crollDamp;
 		modID = new ModID();
 	}
@@ -172,6 +203,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCrollForce(double crollForce) {
+		if (CrollForce == crollForce)
+			return;
+		
 		CrollForce = crollForce;
 		modID = new ModID();
 	}
@@ -181,6 +215,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCDaxial(double cdaxial) {
+		if (CDaxial == cdaxial)
+			return;
+		
 		CDaxial = cdaxial;
 		modID= new ModID();
 	}
@@ -190,6 +227,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setCD(double cD) {
+		if (CD == cD)
+			return;
+		
 		CD = cD;
 		modID = new ModID();
 	}
@@ -206,6 +246,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setPressureCD(double pressureCD) {
+		if (this.pressureCD == pressureCD)
+			return;
+		
 		this.pressureCD = pressureCD;
 		modID = new ModID();
 	}
@@ -221,6 +264,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setBaseCD(double baseCD) {
+		if (this.baseCD == baseCD)
+			return;
+		
 		this.baseCD = baseCD;
 		modID = new ModID();
 	}
@@ -236,6 +282,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setFrictionCD(double frictionCD) {
+		if (this.frictionCD == frictionCD)
+			return;
+		
 		this.frictionCD = frictionCD;
 		modID = new ModID();
 	}
@@ -251,6 +300,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setOverrideCD(double overrideCD) {
+		if (this.overrideCD == overrideCD)
+			return;
+		
 		this.overrideCD = overrideCD;
 		modID = new ModID();
 	}
@@ -266,6 +318,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setPitchDampingMoment(double pitchDampingMoment) {
+		if (this.pitchDampingMoment == pitchDampingMoment)
+			return;
+		
 		this.pitchDampingMoment = pitchDampingMoment;
 		modID = new ModID();
 	}
@@ -275,6 +330,9 @@ public class AerodynamicForces implements Cloneable, Monitorable {
 	}
 
 	public void setYawDampingMoment(double yawDampingMoment) {
+		if (this.yawDampingMoment == yawDampingMoment)
+			return;
+		   
 		this.yawDampingMoment = yawDampingMoment;
 		modID = new ModID();
 	}
diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java
index f71fecb12..33a5e3a30 100644
--- a/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java
+++ b/core/src/main/java/info/openrocket/core/aerodynamics/FlightConditions.java
@@ -82,7 +82,6 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 	public FlightConditions(FlightConfiguration config) {
 		if (config != null)
 			setRefLength(config.getReferenceLength());
-		this.modID = new ModID();
 	}
 
 	/**
@@ -99,9 +98,12 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 	 * fires change event
 	 */
 	public void setRefLength(double length) {
+		if (refLength == length)
+			return;
+		
 		refLength = length;
-
 		refArea = Math.PI * MathUtil.pow2(length / 2);
+
 		fireChangeEvent();
 	}
 
@@ -117,8 +119,12 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 	 * fires change event
 	 */
 	public void setRefArea(double area) {
+		if (refArea == area)
+			return;
+		
 		refArea = area;
 		refLength = MathUtil.safeSqrt(area / Math.PI) * 2;
+
 		fireChangeEvent();
 	}
 
@@ -149,6 +155,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 			this.sinAOA = Math.sin(aoa);
 			this.sincAOA = sinAOA / aoa;
 		}
+
 		fireChangeEvent();
 	}
 
@@ -178,6 +185,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 		} else {
 			this.sincAOA = sinAOA / aoa;
 		}
+
 		fireChangeEvent();
 	}
 
@@ -212,6 +220,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 		if (MathUtil.equals(this.theta, theta))
 			return;
 		this.theta = theta;
+
 		fireChangeEvent();
 	}
 
@@ -238,6 +247,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 			this.beta = MathUtil.safeSqrt(1 - mach * mach);
 		else
 			this.beta = MathUtil.safeSqrt(mach * mach - 1);
+
 		fireChangeEvent();
 	}
 
@@ -294,6 +304,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 			return;
 
 		this.rollRate = rate;
+		
 		fireChangeEvent();
 	}
 
@@ -369,7 +380,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 	public void setAtmosphericConditions(AtmosphericConditions cond) {
 		if (atmosphericConditions.equals(cond))
 			return;
-		modID = new ModID();
+
 		atmosphericConditions = cond;
 		fireChangeEvent();
 	}
@@ -456,6 +467,8 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
 	 * wake up call to listeners
 	 */
 	protected void fireChangeEvent() {
+		modID = new ModID();
+		
 		// Copy the list before iterating to prevent concurrent modification exceptions.
 		EventListener[] listeners = listenerList.toArray(new EventListener[0]);
 		for (EventListener l : listeners) {

From 6c732a7c0de51d3f7e7559ac5132a6b29c2436ef Mon Sep 17 00:00:00 2001
From: JoePfeiffer <joseph@pfeifferfamily.net>
Date: Fri, 26 Jul 2024 10:02:39 -0600
Subject: [PATCH 3/4] comment was wrong

---
 .../info/openrocket/core/models/wind/PinkNoiseWindModel.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java b/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
index 85a435259..6885c063d 100644
--- a/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
+++ b/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
@@ -163,7 +163,7 @@ public class PinkNoiseWindModel implements WindModel {
 
 	@Override
 	public ModID getModID() {
-		return ModID.ZERO; // I'll bet this is wrong...
+		return ModID.ZERO;
 	}
 
 }

From 29aa71f397301183975f6e41a131242abdeb8d84 Mon Sep 17 00:00:00 2001
From: SiboVG <sibo.vangool@hotmail.com>
Date: Fri, 9 Aug 2024 06:12:12 +0200
Subject: [PATCH 4/4] Replace old package name with new one for simulation
 extensions

---
 .../core/file/openrocket/importt/SingleSimulationHandler.java    | 1 +
 1 file changed, 1 insertion(+)

diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java
index 829425a06..ab192e57e 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java
@@ -87,6 +87,7 @@ class SingleSimulationHandler extends AbstractElementHandler {
 			extensions.add(compatibilityExtension(content.trim()));
 		} else if (element.equals("extension") && !StringUtils.isEmpty(attributes.get("extensionid"))) {
 			String id = attributes.get("extensionid");
+			id = id.replace("net.sf.openrocket", "info.openrocket.core");
 			SimulationExtension extension = null;
 			Set<SimulationExtensionProvider> extensionProviders = Application.getInjector()
 					.getInstance(new Key<Set<SimulationExtensionProvider>>() {