diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index 5d605f0ce..6019f66f5 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -1153,16 +1153,20 @@ ComponentCfgDlg.MultiComponentEdit.ttip = You are editing the following co
ComponentCfgDlg.Modify = Modify
ComponentCfgDlg.ModifyComponents = Modify components
-!StageConfig
-StageConfig.tab.Separation = Separation
-StageConfig.tab.Separation.ttip = Stage separation options
-StageConfig.separation.lbl.title = Select when this stage separates:
-StageConfig.separation.lbl.plus = plus
-StageConfig.separation.lbl.seconds = seconds
-StageConfig.parallel.radius = Radial Distance:
-StageConfig.parallel.angle = Angle:
-StageConfig.parallel.count = Number of Copies:
-StageConfig.parallel.plus = plus
+!ComponentAssemblyConfig
+ComponentAssemblyConfig.tab.Separation = Separation
+ComponentAssemblyConfig.tab.Separation.ttip = Stage separation options
+ComponentAssemblyConfig.separation.lbl.title = Select when this stage separates:
+ComponentAssemblyConfig.separation.lbl.plus = plus
+ComponentAssemblyConfig.separation.lbl.seconds = seconds
+ComponentAssemblyConfig.parallel.radius = Radial Distance:
+ComponentAssemblyConfig.parallel.angle = Angle:
+ComponentAssemblyConfig.parallel.count = Number of Copies:
+ComponentAssemblyConfig.parallel.plus = plus
+ComponentAssemblyConfig.but.splitPods = Split Pods
+ComponentAssemblyConfig.but.splitPods.ttip = Split the pod set into separate pod components.
+ComponentAssemblyConfig.but.splitBoosters = Split Boosters
+ComponentAssemblyConfig.but.splitBoosters.ttip = Split the booster into separate booster components.
! FinSetConfig
FinSetCfg.lbl.FinRotation = Fin rotation:
diff --git a/core/resources/l10n/messages_ar.properties b/core/resources/l10n/messages_ar.properties
index 6f5416662..7bbebd0af 100644
--- a/core/resources/l10n/messages_ar.properties
+++ b/core/resources/l10n/messages_ar.properties
@@ -989,15 +989,15 @@ ComponentCfgDlg.Modify = تعديل
ComponentCfgDlg.ModifyComponents = تعديل المكونات
!StageConfig
-StageConfig.tab.Separation = فصل
-StageConfig.tab.Separation.ttip = خيارات فصل المرحلة
-StageConfig.separation.lbl.title = :حدد متى تنفصل هذه المرحلة
-StageConfig.separation.lbl.plus = زائد
-StageConfig.separation.lbl.seconds = ثواني
-StageConfig.parallel.radius = :المسافة الشعاعية
-StageConfig.parallel.angle = :الزاوية
-StageConfig.parallel.count = :عدد النسخ
-StageConfig.parallel.plus = زائد
+ComponentAssemblyConfig.tab.Separation = فصل
+ComponentAssemblyConfig.tab.Separation.ttip = خيارات فصل المرحلة
+ComponentAssemblyConfig.separation.lbl.title = :حدد متى تنفصل هذه المرحلة
+ComponentAssemblyConfig.separation.lbl.plus = زائد
+ComponentAssemblyConfig.separation.lbl.seconds = ثواني
+ComponentAssemblyConfig.parallel.radius = :المسافة الشعاعية
+ComponentAssemblyConfig.parallel.angle = :الزاوية
+ComponentAssemblyConfig.parallel.count = :عدد النسخ
+ComponentAssemblyConfig.parallel.plus = زائد
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = :عدد الزعانف
diff --git a/core/resources/l10n/messages_cs.properties b/core/resources/l10n/messages_cs.properties
index 04106b5b8..99a8b8c79 100644
--- a/core/resources/l10n/messages_cs.properties
+++ b/core/resources/l10n/messages_cs.properties
@@ -676,11 +676,11 @@ ComponentCfgDlg.configuration = konfigurace
ComponentCfgDlg.Modify = Uprav
!StageConfig
-StageConfig.tab.Separation = Oddelen
-StageConfig.tab.Separation.ttip = Vlastnosti oddelen stupne
-StageConfig.separation.lbl.title = Oznac kdy se m tento stupn oddelit:
-StageConfig.separation.lbl.plus = plus
-StageConfig.separation.lbl.seconds = sekundy
+ComponentAssemblyConfig.tab.Separation = Oddelen
+ComponentAssemblyConfig.tab.Separation.ttip = Vlastnosti oddelen stupne
+ComponentAssemblyConfig.separation.lbl.title = Oznac kdy se m tento stupn oddelit:
+ComponentAssemblyConfig.separation.lbl.plus = plus
+ComponentAssemblyConfig.separation.lbl.seconds = sekundy
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = Pocet stabiliztoru:
diff --git a/core/resources/l10n/messages_de.properties b/core/resources/l10n/messages_de.properties
index e888f8944..033a8ff48 100644
--- a/core/resources/l10n/messages_de.properties
+++ b/core/resources/l10n/messages_de.properties
@@ -733,11 +733,11 @@ ComponentCfgDlg.configuration = Konfiguration
ComponentCfgDlg.Modify = Verndern
!StageConfig
-StageConfig.tab.Separation = Stufentrennung
-StageConfig.tab.Separation.ttip = Stufentrennungs-Optionen
-StageConfig.separation.lbl.title = Auswhlen, wenn diese Stufe getrennt wird:
-StageConfig.separation.lbl.plus = plus
-StageConfig.separation.lbl.seconds = Sekunden
+ComponentAssemblyConfig.tab.Separation = Stufentrennung
+ComponentAssemblyConfig.tab.Separation.ttip = Stufentrennungs-Optionen
+ComponentAssemblyConfig.separation.lbl.title = Auswhlen, wenn diese Stufe getrennt wird:
+ComponentAssemblyConfig.separation.lbl.plus = plus
+ComponentAssemblyConfig.separation.lbl.seconds = Sekunden
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = Anzahl der Leitwerke
diff --git a/core/resources/l10n/messages_es.properties b/core/resources/l10n/messages_es.properties
index 49c52554e..6e01fa2a1 100644
--- a/core/resources/l10n/messages_es.properties
+++ b/core/resources/l10n/messages_es.properties
@@ -1051,12 +1051,12 @@ Stage.Stage = Etapa
! StageAction
StageAction.Stage = Etapa
-StageConfig.separation.lbl.plus = m\u00e1s
-StageConfig.separation.lbl.seconds = segundos
-StageConfig.separation.lbl.title = Seleccione el instante de separaci\u00f3n de esta etapa:
+ComponentAssemblyConfig.separation.lbl.plus = m\u00e1s
+ComponentAssemblyConfig.separation.lbl.seconds = segundos
+ComponentAssemblyConfig.separation.lbl.title = Seleccione el instante de separaci\u00f3n de esta etapa:
!StageConfig
-StageConfig.tab.Separation = Separaci\u00f3n
-StageConfig.tab.Separation.ttip = Opciones de separaci\u00f3n de etapa
+ComponentAssemblyConfig.tab.Separation = Separaci\u00f3n
+ComponentAssemblyConfig.tab.Separation.ttip = Opciones de separaci\u00f3n de etapa
StorageOptChooser.checkbox.Compfile = Archivo comprimido
StorageOptChooser.lbl.Saveopt = Guardar opciones
diff --git a/core/resources/l10n/messages_fr.properties b/core/resources/l10n/messages_fr.properties
index 65686aca6..1041d5686 100644
--- a/core/resources/l10n/messages_fr.properties
+++ b/core/resources/l10n/messages_fr.properties
@@ -1044,12 +1044,12 @@ Stage.Stage = Etage
! StageAction
StageAction.Stage = Etage
-StageConfig.separation.lbl.plus = plus
-StageConfig.separation.lbl.seconds = secondes
-StageConfig.separation.lbl.title = Choisir lorsque cet \u00E9tage se s\u00E9pare:
+ComponentAssemblyConfig.separation.lbl.plus = plus
+ComponentAssemblyConfig.separation.lbl.seconds = secondes
+ComponentAssemblyConfig.separation.lbl.title = Choisir lorsque cet \u00E9tage se s\u00E9pare:
!StageConfig
-StageConfig.tab.Separation = S\u00E9paration
-StageConfig.tab.Separation.ttip = Options de s\u00E9paration de l'\u00E9tage
+ComponentAssemblyConfig.tab.Separation = S\u00E9paration
+ComponentAssemblyConfig.tab.Separation.ttip = Options de s\u00E9paration de l'\u00E9tage
StorageOptChooser.checkbox.Compfile = Compresse le fichier
StorageOptChooser.lbl.Saveopt = Options de sauvegarde
diff --git a/core/resources/l10n/messages_it.properties b/core/resources/l10n/messages_it.properties
index 14fa090db..ff1ddd384 100644
--- a/core/resources/l10n/messages_it.properties
+++ b/core/resources/l10n/messages_it.properties
@@ -734,11 +734,11 @@ ComponentCfgDlg.configuration = (configurazione)
ComponentCfgDlg.Modify = Modifica
!StageConfig
-StageConfig.tab.Separation = Separazione
-StageConfig.tab.Separation.ttip = Opzioni della separazione dello stadio
-StageConfig.separation.lbl.title = Seleziona quando questo stadio separa:
-StageConfig.separation.lbl.plus = pi\u00f9
-StageConfig.separation.lbl.seconds = secondi
+ComponentAssemblyConfig.tab.Separation = Separazione
+ComponentAssemblyConfig.tab.Separation.ttip = Opzioni della separazione dello stadio
+ComponentAssemblyConfig.separation.lbl.title = Seleziona quando questo stadio separa:
+ComponentAssemblyConfig.separation.lbl.plus = pi\u00f9
+ComponentAssemblyConfig.separation.lbl.seconds = secondi
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = Numero di pinne:
diff --git a/core/resources/l10n/messages_ja.properties b/core/resources/l10n/messages_ja.properties
index bba01aa9b..c2db3200c 100644
--- a/core/resources/l10n/messages_ja.properties
+++ b/core/resources/l10n/messages_ja.properties
@@ -764,11 +764,11 @@ ComponentCfgDlg.configuration = \u30B3\u30F3\u30D5\u30A3\u30AE\u30E5\u30EC\u30F
ComponentCfgDlg.Modify = \u5909\u66F4
!StageConfig
-StageConfig.tab.Separation = \u5206\u96E2
-StageConfig.tab.Separation.ttip = \u30B9\u30C6\u30FC\u30B8\u5206\u96E2\u30AA\u30D7\u30B7\u30E7\u30F3
-StageConfig.separation.lbl.title = \u30B9\u30C6\u30FC\u30B8\u304C\u5206\u96E2\u3059\u308B\u6642\u523B\u306E\u9078\u629E\uFF1A
-StageConfig.separation.lbl.plus = \u30D7\u30E9\u30B9
-StageConfig.separation.lbl.seconds = \u79D2
+ComponentAssemblyConfig.tab.Separation = \u5206\u96E2
+ComponentAssemblyConfig.tab.Separation.ttip = \u30B9\u30C6\u30FC\u30B8\u5206\u96E2\u30AA\u30D7\u30B7\u30E7\u30F3
+ComponentAssemblyConfig.separation.lbl.title = \u30B9\u30C6\u30FC\u30B8\u304C\u5206\u96E2\u3059\u308B\u6642\u523B\u306E\u9078\u629E\uFF1A
+ComponentAssemblyConfig.separation.lbl.plus = \u30D7\u30E9\u30B9
+ComponentAssemblyConfig.separation.lbl.seconds = \u79D2
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = \u30D5\u30A3\u30F3\u306E\u6570\uFF1A
diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties
index 534b25dce..8b973a025 100644
--- a/core/resources/l10n/messages_nl.properties
+++ b/core/resources/l10n/messages_nl.properties
@@ -940,14 +940,14 @@ ComponentCfgDlg.configuration = configuratie
ComponentCfgDlg.Modify = Wijzigen
!StageConfig
-StageConfig.tab.Separation = Afscheiding
-StageConfig.tab.Separation.ttip = Etape afscheidingsopties
-StageConfig.separation.lbl.title = Selecteer wanneer deze etape afscheidt:
-StageConfig.separation.lbl.plus = plus
-StageConfig.separation.lbl.seconds = seconden
-StageConfig.parallel.radius = Radiale Afstand
-StageConfig.parallel.angle = Hoek
-StageConfig.parallel.count = Aantal kopieën
+ComponentAssemblyConfig.tab.Separation = Afscheiding
+ComponentAssemblyConfig.tab.Separation.ttip = Etape afscheidingsopties
+ComponentAssemblyConfig.separation.lbl.title = Selecteer wanneer deze etape afscheidt:
+ComponentAssemblyConfig.separation.lbl.plus = plus
+ComponentAssemblyConfig.separation.lbl.seconds = seconden
+ComponentAssemblyConfig.parallel.radius = Radiale Afstand
+ComponentAssemblyConfig.parallel.angle = Hoek
+ComponentAssemblyConfig.parallel.count = Aantal kopieën
StageConfig.parallel.offset = Offset-waarde
!EllipticalFinSetConfig
diff --git a/core/resources/l10n/messages_pl.properties b/core/resources/l10n/messages_pl.properties
index f75ac3382..473ce1aa2 100644
--- a/core/resources/l10n/messages_pl.properties
+++ b/core/resources/l10n/messages_pl.properties
@@ -679,11 +679,11 @@ ComponentInfo.EngineBlock = Blokada silnika unieruchamia silnik wewn\u01
ComponentCfgDlg.Modify = Zmodyfikuj
!StageConfig
- StageConfig.tab.Separation = Separacja
- StageConfig.tab.Separation.ttip = Opcje oddzielenia cz\u0142onu
- StageConfig.separation.lbl.title = Ustal moment oddzielenia cz\u0142onu:
- StageConfig.separation.lbl.plus = plus
- StageConfig.separation.lbl.seconds = sek.
+ ComponentAssemblyConfig.tab.Separation = Separacja
+ ComponentAssemblyConfig.tab.Separation.ttip = Opcje oddzielenia cz\u0142onu
+ ComponentAssemblyConfig.separation.lbl.title = Ustal moment oddzielenia cz\u0142onu:
+ ComponentAssemblyConfig.separation.lbl.plus = plus
+ ComponentAssemblyConfig.separation.lbl.seconds = sek.
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = Liczba statecznikw:
diff --git a/core/resources/l10n/messages_pt.properties b/core/resources/l10n/messages_pt.properties
index f4984a407..a0f843f7e 100644
--- a/core/resources/l10n/messages_pt.properties
+++ b/core/resources/l10n/messages_pt.properties
@@ -1020,12 +1020,12 @@ Stage.Stage = Etapa
# StageAction
StageAction.Stage = Est\u00e1gio
-StageConfig.separation.lbl.plus = mais
-StageConfig.separation.lbl.seconds = segundos
-StageConfig.separation.lbl.title = Selecione quando este est\u00e1gio separa:
+ComponentAssemblyConfig.separation.lbl.plus = mais
+ComponentAssemblyConfig.separation.lbl.seconds = segundos
+ComponentAssemblyConfig.separation.lbl.title = Selecione quando este est\u00e1gio separa:
# StageConfig
-StageConfig.tab.Separation = Separa\u00e7\u00e3o
-StageConfig.tab.Separation.ttip = Op\u00e7\u00f5es de separa\u00e7\u00e3o de est\u00e1gio
+ComponentAssemblyConfig.tab.Separation = Separa\u00e7\u00e3o
+ComponentAssemblyConfig.tab.Separation.ttip = Op\u00e7\u00f5es de separa\u00e7\u00e3o de est\u00e1gio
StorageOptChooser.checkbox.Compfile = Compactar arquivos
StorageOptChooser.lbl.Saveopt = Salvar as Op\u00e7\u00f5es
diff --git a/core/resources/l10n/messages_ru.properties b/core/resources/l10n/messages_ru.properties
index 67c595fa3..50b84632f 100644
--- a/core/resources/l10n/messages_ru.properties
+++ b/core/resources/l10n/messages_ru.properties
@@ -968,15 +968,15 @@ ComponentCfgDlg.Modify = \u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C
ComponentCfgDlg.ModifyComponents = \u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u044B
!StageConfig
-StageConfig.tab.Separation = \u0420\u0430\u0437\u0434\u0435\u043B\u0435\u043D\u0438\u0435
-StageConfig.tab.Separation.ttip = \u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0440\u0430\u0437\u0434\u0435\u043B\u0435\u043D\u0438\u044F \u0441\u0442\u0443\u043F\u0435\u043D\u0435\u0439
-StageConfig.separation.lbl.title = \u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435, \u043A\u043E\u0433\u0434\u0430 \u044D\u0442\u0430 \u0441\u0442\u0443\u043F\u0435\u043D\u044C \u043E\u0442\u0434\u0435\u043B\u044F\u0435\u0442\u0441\u044F:
-StageConfig.separation.lbl.plus = \u043F\u043B\u044E\u0441
-StageConfig.separation.lbl.seconds = \u0441\u0435\u043A\u0443\u043D\u0434
-StageConfig.parallel.radius = \u0420\u0430\u0434\u0438\u0430\u043B\u044C\u043D\u043E\u0435 \u0440\u0430\u0441\u0441\u0442\u043E\u044F\u043D\u0438\u0435:
-StageConfig.parallel.angle = \u0423\u0433\u043E\u043B:
-StageConfig.parallel.count = \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u043A\u043E\u043F\u0438\u0439:
-StageConfig.parallel.plus = \u043F\u043B\u044E\u0441
+ComponentAssemblyConfig.tab.Separation = \u0420\u0430\u0437\u0434\u0435\u043B\u0435\u043D\u0438\u0435
+ComponentAssemblyConfig.tab.Separation.ttip = \u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0440\u0430\u0437\u0434\u0435\u043B\u0435\u043D\u0438\u044F \u0441\u0442\u0443\u043F\u0435\u043D\u0435\u0439
+ComponentAssemblyConfig.separation.lbl.title = \u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435, \u043A\u043E\u0433\u0434\u0430 \u044D\u0442\u0430 \u0441\u0442\u0443\u043F\u0435\u043D\u044C \u043E\u0442\u0434\u0435\u043B\u044F\u0435\u0442\u0441\u044F:
+ComponentAssemblyConfig.separation.lbl.plus = \u043F\u043B\u044E\u0441
+ComponentAssemblyConfig.separation.lbl.seconds = \u0441\u0435\u043A\u0443\u043D\u0434
+ComponentAssemblyConfig.parallel.radius = \u0420\u0430\u0434\u0438\u0430\u043B\u044C\u043D\u043E\u0435 \u0440\u0430\u0441\u0441\u0442\u043E\u044F\u043D\u0438\u0435:
+ComponentAssemblyConfig.parallel.angle = \u0423\u0433\u043E\u043B:
+ComponentAssemblyConfig.parallel.count = \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u043A\u043E\u043F\u0438\u0439:
+ComponentAssemblyConfig.parallel.plus = \u043F\u043B\u044E\u0441
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = \u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0441\u0442\u0430\u0431\u0438\u043B\u0438\u0437\u0430\u0442\u043E\u0440\u043E\u0432:
diff --git a/core/resources/l10n/messages_uk_UA.properties b/core/resources/l10n/messages_uk_UA.properties
index 654e106ff..003a6dfe3 100644
--- a/core/resources/l10n/messages_uk_UA.properties
+++ b/core/resources/l10n/messages_uk_UA.properties
@@ -838,11 +838,11 @@ ComponentCfgDlg.configuration = configuration
ComponentCfgDlg.Modify = Modify
!StageConfig
-StageConfig.tab.Separation = Separation
-StageConfig.tab.Separation.ttip = Stage separation options
-StageConfig.separation.lbl.title = Select when this stage separates:
-StageConfig.separation.lbl.plus = plus
-StageConfig.separation.lbl.seconds = seconds
+ComponentAssemblyConfig.tab.Separation = Separation
+ComponentAssemblyConfig.tab.Separation.ttip = Stage separation options
+ComponentAssemblyConfig.separation.lbl.title = Select when this stage separates:
+ComponentAssemblyConfig.separation.lbl.plus = plus
+ComponentAssemblyConfig.separation.lbl.seconds = seconds
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = Number of fins:
diff --git a/core/resources/l10n/messages_zh_CN.properties b/core/resources/l10n/messages_zh_CN.properties
index e71751995..5171a75d4 100644
--- a/core/resources/l10n/messages_zh_CN.properties
+++ b/core/resources/l10n/messages_zh_CN.properties
@@ -1120,12 +1120,12 @@ Stage.Stage = \u706B\u7BAD\u7EA7
! StageAction
StageAction.Stage = \u7EA7
-StageConfig.separation.lbl.plus = \u52A0
-StageConfig.separation.lbl.seconds = \u79D2
-StageConfig.separation.lbl.title = \u8BBE\u5B9A\u5206\u79BB\u65F6\u673A:
+ComponentAssemblyConfig.separation.lbl.plus = \u52A0
+ComponentAssemblyConfig.separation.lbl.seconds = \u79D2
+ComponentAssemblyConfig.separation.lbl.title = \u8BBE\u5B9A\u5206\u79BB\u65F6\u673A:
!StageConfig
-StageConfig.tab.Separation = \u5206\u79BB
-StageConfig.tab.Separation.ttip = \u591A\u7EA7\u5206\u79BB\u9009\u9879
+ComponentAssemblyConfig.tab.Separation = \u5206\u79BB
+ComponentAssemblyConfig.tab.Separation.ttip = \u591A\u7EA7\u5206\u79BB\u9009\u9879
StorageOptChooser.lbl.Saveopt = \u4FDD\u5B58\u9009\u9879
! StorageOptionChooser
diff --git a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java
index 249f8dfeb..672a4990d 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java
@@ -242,7 +242,9 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC
public void clearConfigListeners() {
super.clearConfigListeners();
// StageSeparationConfiguration also has config listeners, so clear them as well
- StageSeparationConfiguration thisConfig = getSeparationConfiguration();
- thisConfig.clearConfigListeners();
+ if (getRoot() instanceof Rocket) { // Root can be different from the rocket if this stage (or its parent) has been removed from the rocket
+ StageSeparationConfiguration thisConfig = getSeparationConfiguration();
+ thisConfig.clearConfigListeners();
+ }
}
}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java
index 82fb557db..45fbd0b2c 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java
@@ -1478,61 +1478,18 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
/**
* Split the fin set into individual fins.
- * @return A list of the new fin sets.
+ *
+ * @return A list of the new fin sets.
*/
- public List splitFins(boolean freezeRocket) {
- final RocketComponent root = getRoot();
- RocketComponent parent = getParent();
- int index = parent.getChildPosition(this);
- int count = getFinCount();
- double base = getBaseRotation();
-
- List splitFins = null; // List of all the split fins
-
- try {
- // Freeze rocket
- if (freezeRocket && root instanceof Rocket) {
- ((Rocket) root).freeze();
- }
-
- // Split the fins
- if (count > 1) {
- parent.removeChild(index);
- splitFins = new ArrayList<>();
- for (int i = 0; i < count; i++) {
- FinSet copy = (FinSet) this.copy();
- copy.setFinCount(1);
- copy.setBaseRotation(base + i * 2 * Math.PI / count);
- copy.setName(copy.getName() + " #" + (i + 1));
- copy.setOverrideMass(getOverrideMass() / getFinCount());
- parent.addChild(copy, index + i);
-
- splitFins.add(copy);
- }
- }
-
- // Split fins for children
- for (RocketComponent listener : configListeners) {
- if (listener instanceof FinSet) {
- ((FinSet) listener).splitFins(false);
- this.removeConfigListener(listener);
- }
- }
- } finally {
- // Unfreeze rocket
- if (freezeRocket && root instanceof Rocket) {
- ((Rocket) root).thaw();
- }
- }
-
- return splitFins;
+ public List splitFins(boolean freezeRocket) {
+ return splitInstances(freezeRocket);
}
/**
* Split the fin set into individual fins.
* @return A list of the new fin sets.
*/
- public List splitFins() {
+ public List splitFins() {
return splitFins(true);
}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/Instanceable.java b/core/src/net/sf/openrocket/rocketcomponent/Instanceable.java
index 47d5c5913..499922bfa 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/Instanceable.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/Instanceable.java
@@ -5,7 +5,7 @@ import net.sf.openrocket.util.Coordinate;
public interface Instanceable {
@Deprecated
- public Coordinate[] getLocations();
+ Coordinate[] getLocations();
/**
* Returns vector coordinates of each instance of this component relative to this component's parent
@@ -16,7 +16,7 @@ public interface Instanceable {
*
* @return coordinates location of each instance relative to component's parent
*/
- public Coordinate[] getInstanceLocations();
+ Coordinate[] getInstanceLocations();
/**
* Returns vector coordinates of each instance of this component relative to this component's reference point (typically front center)
@@ -27,20 +27,20 @@ public interface Instanceable {
*
* @return coordinates location of each instance relative to this component's reference point.
*/
- public Coordinate[] getInstanceOffsets();
+ Coordinate[] getInstanceOffsets();
/**
* How many instances of this component are represented. This should generally be editable.
* @param newCount number of instances to set
*/
- public void setInstanceCount( final int newCount );
+ void setInstanceCount( final int newCount );
/**
* How many instances of this component are represented. This should generally be editable.
*
* @return number of instances this component currently represent.
*/
- public int getInstanceCount();
+ int getInstanceCount();
/**
* Get a human-readable name for this instance arrangement.
@@ -48,6 +48,6 @@ public interface Instanceable {
*
* @return pattern name
*/
- public String getPatternName();
+ String getPatternName();
}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java
index 1c308d11c..1824934bc 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java
@@ -412,7 +412,9 @@ public class Rocket extends ComponentAssembly {
* changes.
*/
public void loadFrom(Rocket source) {
-
+ checkState();
+ mutex.lock("loadFrom");
+
// Store list of components to invalidate after event has been fired
List toInvalidate = this.copyFrom(source);
@@ -453,6 +455,8 @@ public class Rocket extends ComponentAssembly {
for (RocketComponent c : toInvalidate) {
c.invalidate();
}
+
+ mutex.unlock("loadFrom");
}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
index d366a73f4..080a533a2 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
@@ -15,6 +15,7 @@ import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.logging.WarningSet;
+import net.sf.openrocket.rocketcomponent.position.AnglePositionable;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.util.ORColor;
@@ -1088,6 +1089,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
public int getInstanceCount() {
return 1;
}
+
+ public void setInstanceCount(int count) {
+ // Do nothing
+ log.warn("setInstanceCount called on component that does not support multiple instances");
+ }
/**
* Get the user-defined name of the component.
@@ -2382,12 +2388,16 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
*/
public final RocketComponent findComponent(String idToFind) {
checkState();
+ mutex.lock("findComponent");
Iterator iter = this.iterator(true);
while (iter.hasNext()) {
final RocketComponent c = iter.next();
- if (c.getID().equals(idToFind))
+ if (c.getID().equals(idToFind)) {
+ mutex.unlock("findComponent");
return c;
+ }
}
+ mutex.unlock("findComponent");
return null;
}
@@ -2440,6 +2450,67 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
return c;
}
+ /**
+ * Split the current multi-instance component into multiple single-instance components.
+ * @param freezeRocket whether to freeze the rocket while splitting
+ * @return list of all the split components
+ */
+ public List splitInstances(boolean freezeRocket) {
+ final Rocket rocket = getRocket();
+ RocketComponent parent = getParent();
+ int index = parent.getChildPosition(this);
+ int count = getInstanceCount();
+ double angleOffset = getAngleOffset();
+
+ List splitComponents = null; // List of all the split components
+
+ try {
+ // Freeze rocket
+ if (freezeRocket) {
+ rocket.freeze();
+ }
+
+ // Split the components
+ if (count > 1) {
+ parent.removeChild(index, true); // Remove the original component
+ splitComponents = new java.util.ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ RocketComponent copy = this.copy();
+ copy.setInstanceCount(1);
+ if (copy instanceof AnglePositionable) {
+ ((AnglePositionable) copy).setAngleOffset(angleOffset + i * 2 * Math.PI / count);
+ }
+ copy.setName(copy.getName() + " #" + (i + 1));
+ copy.setOverrideMass(getOverrideMass() / count);
+ parent.addChild(copy, index + i, true); // Add the new component
+
+ splitComponents.add(copy);
+ }
+ }
+
+ // Split components for listeners
+ for (RocketComponent listener : configListeners) {
+ if (listener.getClass().isAssignableFrom(this.getClass())) {
+ listener.splitInstances(false);
+ this.removeConfigListener(listener);
+ }
+ }
+ } finally {
+ // Unfreeze rocket
+ if (freezeRocket) {
+ rocket.thaw();
+ }
+ }
+
+ fireComponentChangeEvent(ComponentChangeEvent.TREE_CHANGE);
+
+ return splitComponents;
+ }
+
+ public List splitInstances() {
+ return splitInstances(true);
+ }
+
/////////// Event handling //////////
//
// Listener lists are provided by the root Rocket component,
diff --git a/swing/src/net/sf/openrocket/gui/adaptors/DoubleModel.java b/swing/src/net/sf/openrocket/gui/adaptors/DoubleModel.java
index 6442c6b4a..171b23b04 100644
--- a/swing/src/net/sf/openrocket/gui/adaptors/DoubleModel.java
+++ b/swing/src/net/sf/openrocket/gui/adaptors/DoubleModel.java
@@ -1000,6 +1000,9 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
log.warn("Invalidating " + this + " while still having listeners " + listeners);
}
listeners.clear();
+ if (source instanceof ChangeSource) {
+ ((ChangeSource) source).removeChangeListener(this);
+ }
MemoryManagement.collectable(this);
}
diff --git a/swing/src/net/sf/openrocket/gui/configdialog/AxialStageConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/AxialStageConfig.java
index 8e3cbcb40..0ffbe50e1 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/AxialStageConfig.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/AxialStageConfig.java
@@ -31,8 +31,8 @@ public class AxialStageConfig extends ComponentAssemblyConfig {
// Stage separation config (for non-first stage)
if (component.getStageNumber() > 0) {
JPanel tab = separationTab((AxialStage) component);
- tabbedPane.insertTab(trans.get("StageConfig.tab.Separation"), null, tab,
- trans.get("StageConfig.tab.Separation.ttip"), 0);
+ tabbedPane.insertTab(trans.get("ComponentAssemblyConfig.tab.Separation"), null, tab,
+ trans.get("ComponentAssemblyConfig.tab.Separation.ttip"), 0);
tabbedPane.setSelectedIndex(0);
}
@@ -49,7 +49,7 @@ public class AxialStageConfig extends ComponentAssemblyConfig {
JPanel panel = new JPanel(new MigLayout());
// Select separation event
- panel.add(new StyledLabel(trans.get("StageConfig.separation.lbl.title") + " " + CommonStrings.dagger, Style.BOLD),
+ panel.add(new StyledLabel(trans.get("ComponentAssemblyConfig.separation.lbl.title") + " " + CommonStrings.dagger, Style.BOLD),
"spanx, gaptop unrel, wrap 30lp");
StageSeparationConfiguration sepConfig = stage.getSeparationConfiguration();
@@ -61,7 +61,7 @@ public class AxialStageConfig extends ComponentAssemblyConfig {
order.add(combo);
// ... and delay
- panel.add(new JLabel(trans.get("StageConfig.separation.lbl.plus")));
+ panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.plus")));
DoubleModel dm = new DoubleModel( sepConfig, "SeparationDelay", 0);
JSpinner spin = new JSpinner(dm.getSpinnerModel());
@@ -70,7 +70,7 @@ public class AxialStageConfig extends ComponentAssemblyConfig {
order.add(((SpinnerEditor)spin.getEditor()).getTextField());
//// seconds
- panel.add(new JLabel(trans.get("StageConfig.separation.lbl.seconds")), "wrap unrel");
+ panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.seconds")), "wrap unrel");
panel.add(new StyledLabel(CommonStrings.override_description, -1), "spanx, pushy, wrap para");
diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ComponentAssemblyConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ComponentAssemblyConfig.java
index 0002f2295..fcc47c85b 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/ComponentAssemblyConfig.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/ComponentAssemblyConfig.java
@@ -1,11 +1,13 @@
package net.sf.openrocket.gui.configdialog;
import javax.swing.ComboBoxModel;
+import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
+import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
@@ -15,27 +17,35 @@ import net.sf.openrocket.gui.adaptors.EnumModel;
import net.sf.openrocket.gui.adaptors.IntegerModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.widgets.SelectColorButton;
import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.Markers;
import net.sf.openrocket.rocketcomponent.ComponentAssembly;
import net.sf.openrocket.rocketcomponent.ParallelStage;
import net.sf.openrocket.rocketcomponent.PodSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.position.AxialMethod;
import net.sf.openrocket.rocketcomponent.position.RadiusMethod;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
+import java.util.ArrayList;
import java.util.EventObject;
+import java.util.List;
@SuppressWarnings("serial")
public class ComponentAssemblyConfig extends RocketComponentConfig {
private static final Translator trans = Application.getTranslator();
+ private static final Logger log = LoggerFactory.getLogger(ComponentAssemblyConfig.class);
private final RocketComponent component;
+
+ private JButton split = null;
public ComponentAssemblyConfig(OpenRocketDocument document, RocketComponent component, JDialog parent) {
super(document, component, parent);
@@ -46,6 +56,8 @@ public class ComponentAssemblyConfig extends RocketComponentConfig {
tabbedPane.insertTab( trans.get("RocketCompCfg.tab.Assembly"), null, parallelTab( (ComponentAssembly)component ),
trans.get("RocketCompCfg.tab.AssemblyComment"), 0);
tabbedPane.setSelectedIndex(0);
+
+ addSplitButton();
}
}
@@ -62,7 +74,7 @@ public class ComponentAssemblyConfig extends RocketComponentConfig {
order.add(radiusMethodCombo);
// set radial distance
- JLabel radiusLabel = new JLabel(trans.get("StageConfig.parallel.radius"));
+ JLabel radiusLabel = new JLabel(trans.get("ComponentAssemblyConfig.parallel.radius"));
motherPanel.add( radiusLabel , "align left");
//radiusMethodModel.addEnableComponent(radiusLabel, false);
DoubleModel radiusModel = new DoubleModel( boosters, "RadiusOffset", UnitGroup.UNITS_LENGTH, 0);
@@ -85,7 +97,7 @@ public class ComponentAssemblyConfig extends RocketComponentConfig {
});
// set angle
- JLabel angleLabel = new JLabel(trans.get("StageConfig.parallel.angle"));
+ JLabel angleLabel = new JLabel(trans.get("ComponentAssemblyConfig.parallel.angle"));
motherPanel.add( angleLabel, "align left");
DoubleModel angleModel = new DoubleModel( boosters, "AngleOffset", 1.0, UnitGroup.UNITS_ANGLE, -Math.PI, Math.PI);
@@ -98,7 +110,7 @@ public class ComponentAssemblyConfig extends RocketComponentConfig {
motherPanel.add(new BasicSlider(angleModel.getSliderModel(-Math.PI, Math.PI)), "gapleft para, growx 2, wrap");
// set multiplicity
- JLabel countLabel = new JLabel(trans.get("StageConfig.parallel.count"));
+ JLabel countLabel = new JLabel(trans.get("ComponentAssemblyConfig.parallel.count"));
motherPanel.add( countLabel, "align left");
IntegerModel countModel = new IntegerModel( boosters, "InstanceCount", 1);
@@ -112,4 +124,64 @@ public class ComponentAssemblyConfig extends RocketComponentConfig {
return motherPanel;
}
+
+ @Override
+ public void updateFields() {
+ super.updateFields();
+ if (split != null) {
+ split.setEnabled(component.getInstanceCount() > 1);
+ }
+ }
+
+ private void addSplitButton() {
+ //// Split fins
+ final String btnText;
+ final String btnTextTtip;
+ final boolean freezeRocket;
+ if (PodSet.class.isAssignableFrom(component.getClass())) {
+ btnText = trans.get("ComponentAssemblyConfig.but.splitPods");
+ btnTextTtip = trans.get("ComponentAssemblyConfig.but.splitPods.ttip");
+ freezeRocket = true;
+ } else if (ParallelStage.class.isAssignableFrom(component.getClass())) {
+ btnText = trans.get("ComponentAssemblyConfig.but.splitBoosters");
+ btnTextTtip = trans.get("ComponentAssemblyConfig.but.splitBoosters.ttip");
+ freezeRocket = false;
+ } else {
+ return;
+ }
+ split = new SelectColorButton(btnText);
+ split.setToolTipText(btnTextTtip);
+ split.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.info(Markers.USER_MARKER, "Splitting " + component.getComponentName() + " into separate assemblies, instance count=" +
+ component.getInstanceCount());
+
+ // This is a bit awkward, we need to store the listeners before closing the dialog, because closing it
+ // will remove them. We then add them back before the split and remove them afterwards.
+ List listeners = new ArrayList<>(component.getConfigListeners());
+
+
+ // Do change in future for overall safety
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ disposeDialog();
+
+ document.startUndo("Split assembly");
+ for (RocketComponent listener : listeners) {
+ component.addConfigListener(listener);
+ }
+ component.splitInstances(freezeRocket);
+ component.clearConfigListeners();
+ document.stopUndo();
+ }
+ });
+ }
+ });
+ split.setEnabled(component.getInstanceCount() > 1);
+
+ addButtons(split);
+ order.add(split);
+ }
}
diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java
index b11871fb0..0c307eec1 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java
@@ -339,6 +339,7 @@ public class RocketComponentConfig extends JPanel {
}
protected void disposeDialog() {
+ invalidate();
if (parent != null) {
if (parent instanceof ComponentConfigDialog) {
ComponentConfigDialog.disposeDialog();
@@ -700,6 +701,7 @@ public class RocketComponentConfig extends JPanel {
panel.add(checkboxes, "growx 1, gapright 20lp");
m = new DoubleModel(component, "OverrideCD", UnitGroup.UNITS_COEFFICIENT, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+ register(m);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java
index bd90c2617..55eaf5267 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java
@@ -82,7 +82,7 @@ public class SeparationSelectionDialog extends JDialog {
panel.add(event, "wrap rel");
// ... and delay
- panel.add(new JLabel(trans.get("StageConfig.separation.lbl.plus")), "alignx 100%");
+ panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.plus")), "alignx 100%");
final DoubleModel delay = new DoubleModel(newConfiguration, "SeparationDelay", UnitGroup.UNITS_SHORT_TIME, 0);
JSpinner spin = new JSpinner(delay.getSpinnerModel());
@@ -90,7 +90,7 @@ public class SeparationSelectionDialog extends JDialog {
panel.add(spin, "span, split");
//// seconds
- panel.add(new JLabel(trans.get("StageConfig.separation.lbl.seconds")), "wrap para");
+ panel.add(new JLabel(trans.get("ComponentAssemblyConfig.separation.lbl.seconds")), "wrap para");
panel.add(new JPanel(), "span, split, growx");