[#2377] Add booster/pod split functionality

This commit is contained in:
SiboVG 2024-01-21 02:01:18 +01:00
parent d2fc662a56
commit 940b913dfa
24 changed files with 270 additions and 155 deletions

View File

@ -1153,16 +1153,20 @@ ComponentCfgDlg.MultiComponentEdit.ttip = <html>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:

View File

@ -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 = :عدد الزعانف

View File

@ -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 stabilizátoru:

View File

@ -733,11 +733,11 @@ ComponentCfgDlg.configuration = Konfiguration
ComponentCfgDlg.Modify = Verändern
!StageConfig
StageConfig.tab.Separation = Stufentrennung
StageConfig.tab.Separation.ttip = Stufentrennungs-Optionen
StageConfig.separation.lbl.title = Auswählen, 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 = Auswählen, wenn diese Stufe getrennt wird:
ComponentAssemblyConfig.separation.lbl.plus = plus
ComponentAssemblyConfig.separation.lbl.seconds = Sekunden
!EllipticalFinSetConfig
EllipticalFinSetCfg.Nbroffins = Anzahl der Leitwerke

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -679,11 +679,11 @@ ComponentInfo.EngineBlock = <b>Blokada silnika</b> 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 stateczników:

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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<FinSet> splitFins(boolean freezeRocket) {
final RocketComponent root = getRoot();
RocketComponent parent = getParent();
int index = parent.getChildPosition(this);
int count = getFinCount();
double base = getBaseRotation();
List<FinSet> 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<RocketComponent> splitFins(boolean freezeRocket) {
return splitInstances(freezeRocket);
}
/**
* Split the fin set into individual fins.
* @return A list of the new fin sets.
*/
public List<FinSet> splitFins() {
public List<RocketComponent> splitFins() {
return splitFins(true);
}

View File

@ -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 <b>this</b> 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();
}

View File

@ -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<RocketComponent> toInvalidate = this.copyFrom(source);
@ -453,6 +455,8 @@ public class Rocket extends ComponentAssembly {
for (RocketComponent c : toInvalidate) {
c.invalidate();
}
mutex.unlock("loadFrom");
}

View File

@ -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<RocketComponent> 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<RocketComponent> splitInstances(boolean freezeRocket) {
final Rocket rocket = getRocket();
RocketComponent parent = getParent();
int index = parent.getChildPosition(this);
int count = getInstanceCount();
double angleOffset = getAngleOffset();
List<RocketComponent> 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<RocketComponent> splitInstances() {
return splitInstances(true);
}
/////////// Event handling //////////
//
// Listener lists are provided by the root Rocket component,

View File

@ -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);
}

View File

@ -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");

View File

@ -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<RocketComponent> 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);
}
}

View File

@ -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));

View File

@ -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");