[#2068] Use generalized motor config substitutor

This commit is contained in:
SiboVG 2023-02-23 16:15:30 +01:00
parent 5b921eebaf
commit a621bed20a
6 changed files with 339 additions and 298 deletions

View File

@ -228,8 +228,9 @@ edtmotorconfdlg.tbl.Separationheader = Separation
RenameConfigDialog.title = Rename Configuration
RenameConfigDialog.lbl.name = Name for flight configuration:
RenameConfigDialog.but.reset = Reset to default
RenameConfigDialog.lbl.infoMotors = The text '<b>{motors}</b>' will be replaced with the <b>motor designation(s)</b>.
RenameConfigDialog.lbl.infoManufacturers = The text '<b>{manufacturers}</b>' will be replaced with the <b>motor manufacturer(s)</b>.
RenameConfigDialog.lbl.infoMotors = The text '<b>{motors}</b>' will be replaced with the <b>motor designation(s).</b><br><pre>\te.g. '{motors} \u2192 'M1350-0'</pre>
RenameConfigDialog.lbl.infoManufacturers = The text '<b>{manufacturers}</b>' will be replaced with the <b>motor manufacturer(s).</b><br><pre>\te.g. '{manufacturers}' \u2192 'AeroTech'</pre>
RenameConfigDialog.lbl.infoCombination = A <b>combination</b> of the above can be used.<br><pre>\te.g. '{motors, manufacturers}' \u2192 'M1350-0, AeroTech'</pre>
! Example design dialog

View File

@ -0,0 +1,250 @@
package net.sf.openrocket.formatting;
import com.google.inject.Inject;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.Chars;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* General substitutor for motor configurations. This currently includes substitutions for
* - {motors} - the motor designation (e.g. "M1350-0")
* - {manufacturers} - the motor manufacturer (e.g. "AeroTech")
* - a combination of motors and manufacturers, e.g. {motors | manufacturers} -> "M1350-0 | AeroTech"
* You can choose which comes first and what the separator is. E.g. {manufacturers, motors} -> "AeroTech, M1350-0".
*
* <p>
* This substitutor is added through injection. All substitutors with the "@Plugin" tag in the formatting package will
* be included automatically.
*/
@Plugin
public class MotorConfigurationSubstitutor implements RocketSubstitutor {
public static final String SUBSTITUTION_START = "{";
public static final String SUBSTITUTION_END = "}";
public static final String SUBSTITUTION_MOTORS = "motors";
public static final String SUBSTITUTION_MANUFACTURERS = "manufacturers";
// Substitutions for combinations of motors and manufacturers
private static final String SUBSTITUTION_PATTERN = "\\" + SUBSTITUTION_START +
"(" + SUBSTITUTION_MOTORS + "|" + SUBSTITUTION_MANUFACTURERS + ")" +
"(.*?)" +
"(" + SUBSTITUTION_MOTORS + "|" + SUBSTITUTION_MANUFACTURERS + ")" +
"\\" + SUBSTITUTION_END;
@Inject
private Translator trans;
@Override
public boolean containsSubstitution(String input) {
return getSubstitutionContent(input) != null;
}
@Override
public String substitute(String input, Rocket rocket, FlightConfigurationId configId) {
String description = getConfigurationSubstitution(input, rocket, configId);
String substitutionString = getSubstiutionString(input);
if (substitutionString != null) {
return input.replace(substitutionString, description);
}
return input;
}
@Override
public Map<String, String> getDescriptions() {
return null;
}
public String getConfigurationSubstitution(String input, Rocket rocket, FlightConfigurationId fcid) {
StringBuilder configurations = new StringBuilder();
int motorCount = 0;
// Iterate over each stage and store the manufacturer of each motor
List<List<String>> list = new ArrayList<>();
List<String> currentList = new ArrayList<>();
String[] content = getSubstitutionContent(input);
if (content == null) {
return "";
}
FlightConfiguration config = rocket.getFlightConfiguration(fcid);
for (RocketComponent c : rocket) {
if (c instanceof AxialStage) {
currentList = new ArrayList<>();
list.add(currentList);
} else if (c instanceof MotorMount) {
MotorMount mount = (MotorMount) c;
MotorConfiguration inst = mount.getMotorConfig(fcid);
Motor motor = inst.getMotor();
if (mount.isMotorMount() && config.isComponentActive(mount) && (motor != null)) {
String motorDesignation = motor.getDesignation(inst.getEjectionDelay());
String manufacturer = "";
if (motor instanceof ThrustCurveMotor) {
manufacturer = ((ThrustCurveMotor) motor).getManufacturer().getDisplayName();
}
for (int i = 0; i < mount.getMotorCount(); i++) {
if (content.length == 2) {
if (SUBSTITUTION_MOTORS.equals(content[1])) {
currentList.add(motorDesignation);
} else if (SUBSTITUTION_MANUFACTURERS.equals(content[1])) {
currentList.add(manufacturer);
} else {
continue;
}
} else if (content.length == 4) {
String configString;
if (content[1].equals(SUBSTITUTION_MOTORS)) {
configString = motorDesignation;
} else if (content[1].equals(SUBSTITUTION_MANUFACTURERS)) {
configString = manufacturer;
} else {
continue;
}
configString += content[2];
if (content[3].equals(SUBSTITUTION_MOTORS)) {
configString += motorDesignation;
} else if (content[3].equals(SUBSTITUTION_MANUFACTURERS)) {
configString += manufacturer;
} else {
continue;
}
currentList.add(configString);
} else {
continue;
}
motorCount++;
}
}
}
}
if (motorCount == 0) {
return trans.get("Rocket.motorCount.Nomotor");
}
// Change multiple occurrences of a motor to n x motor
List<String> stages = new ArrayList<>();
for (List<String> stage : list) {
String stageName = "";
String previous = null;
int count = 0;
Collections.sort(stage);
for (String current : stage) {
if (current.equals(previous)) {
count++;
} else {
if (previous != null) {
String s = count > 1 ? count + Chars.TIMES + previous : previous;
stageName = stageName.equals("") ? s : stageName + "," + s;
}
previous = current;
count = 1;
}
}
if (previous != null) {
String s = count > 1 ? "" + count + Chars.TIMES + previous : previous;
stageName = stageName.equals("") ? s : stageName + "," + s;
}
stages.add(stageName);
}
for (int i = 0; i < stages.size(); i++) {
String s = stages.get(i);
if (s.equals("") && config.isStageActive(i)) {
s = trans.get("Rocket.motorCount.noStageMotors");
}
configurations.append(i == 0 ? s : "; " + s);
}
return configurations.toString();
}
/**
* Returns which string in input should be replaced, or null if no text needs to be replaced.
* @param input The input string
* @return The string to replace, or null if no text needs to be replaced.
*/
private static String getSubstiutionString(String input) {
String[] content = getSubstitutionContent(input);
if (content != null) {
return content[0];
}
return null;
}
/**
* Fills in the content of the substitution tag and the separator.
* If there are both a motor and a manufacturer substitution tag, the array will contain the following:
* [0] = The full tag, including substitution start and end
* [1] = The motor/manufacturer substitution tag, depending on which one was found first.
* if there are two substitution tags, the array will also contain the following:
* ([2] = The separator)
* ([3] = The motor/manufacturer substitution tag, depending on which one was found first.)
* @param input The input string
* @return The content of the substitution tag and the separator, or null if no text needs to be replaced.
*/
private static String[] getSubstitutionContent(String input) {
// First try with only the motors tag
String pattern = "\\" + SUBSTITUTION_START + "(" + SUBSTITUTION_MOTORS + ")" + "\\" + SUBSTITUTION_END;
Pattern regexPattern = Pattern.compile(pattern);
Matcher matcher = regexPattern.matcher(input);
if (matcher.find()) {
String[] content = new String[2];
content[0] = matcher.group(0);
content[1] = matcher.group(1);
return content;
}
// First try with only the manufacturers tag
pattern = "\\" + SUBSTITUTION_START + "(" + SUBSTITUTION_MANUFACTURERS + ")" + "\\" + SUBSTITUTION_END;
regexPattern = Pattern.compile(pattern);
matcher = regexPattern.matcher(input);
if (matcher.find()) {
String[] content = new String[2];
content[0] = matcher.group(0);
content[1] = matcher.group(1);
return content;
}
// Then try combined patterns
pattern = SUBSTITUTION_PATTERN;
regexPattern = Pattern.compile(pattern);
matcher = regexPattern.matcher(input);
if (matcher.find()) {
String[] content = new String[4];
content[0] = matcher.group(0);
content[1] = matcher.group(1);
if (matcher.groupCount() >= 3) {
content[2] = matcher.group(2);
content[3] = matcher.group(3);
for (int i = 4; i < matcher.groupCount(); i++) {
content[3] += matcher.group(i);
}
}
return content;
}
return null;
}
}

View File

@ -1,148 +0,0 @@
package net.sf.openrocket.formatting;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.google.inject.Inject;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.Chars;
@Plugin
public class MotorDescriptionSubstitutor implements RocketSubstitutor {
public static final String SUBSTITUTION = "{motors}";
@Inject
private Translator trans;
@Override
public boolean containsSubstitution(String str) {
return str.contains(SUBSTITUTION);
}
@Override
public String substitute(String str, Rocket rocket, FlightConfigurationId configId) {
String description = getMotorConfigurationDescription(rocket, configId);
return str.replace(SUBSTITUTION, description);
}
@Override
public Map<String, String> getDescriptions() {
Map<String, String> desc = new HashMap<String, String>();
desc.put(SUBSTITUTION, trans.get("MotorDescriptionSubstitutor.description"));
return null;
}
public String getMotorConfigurationDescription(Rocket rocket, FlightConfigurationId fcid) {
String name;
int motorCount = 0;
// Generate the description
// First iterate over each stage and store the designations of each motor
List<List<String>> list = new ArrayList<List<String>>();
List<String> currentList = Collections.emptyList();
FlightConfiguration config = rocket.getFlightConfiguration(fcid);
for (RocketComponent c : rocket) {
if (c instanceof AxialStage) {
currentList = new ArrayList<>();
list.add(currentList);
} else if (c instanceof MotorMount) {
MotorMount mount = (MotorMount) c;
MotorConfiguration inst = mount.getMotorConfig(fcid);
Motor motor = inst.getMotor();
if (mount.isMotorMount() && config.isComponentActive(mount) && motor != null) {
String designation = motor.getDesignation(inst.getEjectionDelay());
for (int i = 0; i < mount.getMotorCount(); i++) {
currentList.add(designation);
motorCount++;
}
}
}
}
if (motorCount == 0) {
return trans.get("Rocket.motorCount.Nomotor");
}
// Change multiple occurrences of a motor to n x motor
List<String> stages = new ArrayList<String>();
for (List<String> stage : list) {
String stageName = "";
String previous = null;
int count = 0;
Collections.sort(stage);
for (String current : stage) {
if (current.equals(previous)) {
count++;
} else {
if (previous != null) {
String s = "";
if (count > 1) {
s = "" + count + Chars.TIMES + previous;
} else {
s = previous;
}
if (stageName.equals(""))
stageName = s;
else
stageName = stageName + "," + s;
}
previous = current;
count = 1;
}
}
if (previous != null) {
String s = "";
if (count > 1) {
s = "" + count + Chars.TIMES + previous;
} else {
s = previous;
}
if (stageName.equals(""))
stageName = s;
else
stageName = stageName + "," + s;
}
stages.add(stageName);
}
name = "";
for (int i = 0; i < stages.size(); i++) {
String s = stages.get(i);
if (s.equals("") && config.isStageActive(i))
s = trans.get("Rocket.motorCount.noStageMotors");
if (i == 0)
name = name + s;
else
name = name + "; " + s;
}
return name;
}
}

View File

@ -1,146 +0,0 @@
package net.sf.openrocket.formatting;
import com.google.inject.Inject;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.Chars;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Plugin
public class MotorManufacturerSubstitutor implements RocketSubstitutor {
public static final String SUBSTITUTION = "{manufacturers}";
@Inject
private Translator trans;
@Override
public boolean containsSubstitution(String str) {
return str.contains(SUBSTITUTION);
}
@Override
public String substitute(String str, Rocket rocket, FlightConfigurationId configId) {
String description = getMotorConfigurationManufacturer(rocket, configId);
return str.replace(SUBSTITUTION, description);
}
@Override
public Map<String, String> getDescriptions() {
Map<String, String> desc = new HashMap<>();
desc.put(SUBSTITUTION, trans.get("MotorManufacturerSubstitutor.description"));
return null;
}
public String getMotorConfigurationManufacturer(Rocket rocket, FlightConfigurationId fcid) {
String manufacturers;
int motorCount = 0;
// Generate the description
// First iterate over each stage and store the manufacturer of each motor
List<List<String>> list = new ArrayList<>();
List<String> currentList = Collections.emptyList();
FlightConfiguration config = rocket.getFlightConfiguration(fcid);
for (RocketComponent c : rocket) {
if (c instanceof AxialStage) {
currentList = new ArrayList<>();
list.add(currentList);
} else if (c instanceof MotorMount) {
MotorMount mount = (MotorMount) c;
MotorConfiguration inst = mount.getMotorConfig(fcid);
Motor motor = inst.getMotor();
if (mount.isMotorMount() && config.isComponentActive(mount) && motor instanceof ThrustCurveMotor) {
String manufacturer = ((ThrustCurveMotor) motor).getManufacturer().getDisplayName();
for (int i = 0; i < mount.getMotorCount(); i++) {
currentList.add(manufacturer);
motorCount++;
}
}
}
}
if (motorCount == 0) {
return trans.get("Rocket.motorCount.Nomotor");
}
// Change multiple occurrences of a motor to n x motor
List<String> stages = new ArrayList<>();
for (List<String> stage : list) {
String stageName = "";
String previous = null;
int count = 0;
Collections.sort(stage);
for (String current : stage) {
if (current.equals(previous)) {
count++;
} else {
if (previous != null) {
String s = "";
if (count > 1) {
s = "" + count + Chars.TIMES + previous;
} else {
s = previous;
}
if (stageName.equals(""))
stageName = s;
else
stageName = stageName + "," + s;
}
previous = current;
count = 1;
}
}
if (previous != null) {
String s = "";
if (count > 1) {
s = "" + count + Chars.TIMES + previous;
} else {
s = previous;
}
if (stageName.equals(""))
stageName = s;
else
stageName = stageName + "," + s;
}
stages.add(stageName);
}
manufacturers = "";
for (int i = 0; i < stages.size(); i++) {
String s = stages.get(i);
if (s.equals("") && config.isStageActive(i))
s = trans.get("Rocket.motorCount.noStageMotors");
if (i == 0)
manufacturers = manufacturers + s;
else
manufacturers = manufacturers + "; " + s;
}
return manufacturers;
}
}

View File

@ -584,6 +584,8 @@ public class FlightConfigurationTest extends BaseTestCase {
public void testName() {
Rocket rocket = TestRockets.makeFalcon9Heavy();
FlightConfiguration selected = rocket.getSelectedConfiguration();
// Test only motors or only manufacturers
selected.setName("[{motors}] - [{manufacturers}]");
selected.setAllStages();
@ -598,6 +600,87 @@ public class FlightConfigurationTest extends BaseTestCase {
selected.setAllStages();
selected._setStageActive(0, false);
assertEquals("[; M1350-0; 4\u00D7G77-0] - [; AeroTech; 4\u00D7AeroTech]", selected.getName());
// Test combination of motors and manufacturers
selected.setName("[{motors manufacturers}] -- [{manufacturers}] - [{motors}]");
selected.setAllStages();
assertEquals("[[Rocket.motorCount.noStageMotors]; M1350-0 AeroTech; 4\u00D7G77-0 AeroTech] -- [[Rocket.motorCount.noStageMotors]; AeroTech; 4\u00D7AeroTech] - [[Rocket.motorCount.noStageMotors]; M1350-0; 4\u00D7G77-0]", selected.getName());
selected.setOnlyStage(0);
assertEquals("[[Rocket.motorCount.Nomotor]] -- [[Rocket.motorCount.Nomotor]] - [[Rocket.motorCount.Nomotor]]", selected.getName());
selected.setOnlyStage(1);
assertEquals("[; M1350-0 AeroTech; ] -- [; AeroTech; ] - [; M1350-0; ]", selected.getName());
selected.setAllStages();
selected._setStageActive(0, false);
assertEquals("[; M1350-0 AeroTech; 4\u00D7G77-0 AeroTech] -- [; AeroTech; 4\u00D7AeroTech] - [; M1350-0; 4\u00D7G77-0]", selected.getName());
// Test combination of manufacturers and motors
selected.setName("[{manufacturers | motors}]");
selected.setAllStages();
assertEquals("[[Rocket.motorCount.noStageMotors]; AeroTech | M1350-0; 4\u00D7AeroTech | G77-0]", selected.getName());
selected.setOnlyStage(0);
assertEquals("[[Rocket.motorCount.Nomotor]]", selected.getName());
selected.setOnlyStage(1);
assertEquals("[; AeroTech | M1350-0; ]", selected.getName());
selected.setAllStages();
selected._setStageActive(0, false);
assertEquals("[; AeroTech | M1350-0; 4\u00D7AeroTech | G77-0]", selected.getName());
// Test empty tags
selected.setName("{}");
selected.setAllStages();
assertEquals("{}", selected.getName());
selected.setOnlyStage(0);
assertEquals("{}", selected.getName());
selected.setOnlyStage(1);
assertEquals("{}", selected.getName());
selected.setAllStages();
selected._setStageActive(0, false);
assertEquals("{}", selected.getName());
// Test invalid tags (1)
selected.setName("{motorsm}");
selected.setAllStages();
assertEquals("{motorsm}", selected.getName());
selected.setOnlyStage(0);
assertEquals("{motorsm}", selected.getName());
selected.setOnlyStage(1);
assertEquals("{motorsm}", selected.getName());
selected.setAllStages();
selected._setStageActive(0, false);
assertEquals("{motorsm}", selected.getName());
// Test invalid tags (2)
selected.setName("{motors manufacturers '}");
selected.setAllStages();
assertEquals("{motors manufacturers '}", selected.getName());
selected.setOnlyStage(0);
assertEquals("{motors manufacturers '}", selected.getName());
selected.setOnlyStage(1);
assertEquals("{motors manufacturers '}", selected.getName());
selected.setAllStages();
selected._setStageActive(0, false);
assertEquals("{motors manufacturers '}", selected.getName());
}
@Test

View File

@ -70,8 +70,9 @@ public class RenameConfigDialog extends JDialog {
// {motors} & {manufacturers} info
String text = "<html>" + CommonStrings.dagger + " " + trans.get("RenameConfigDialog.lbl.infoMotors")
+ "<br> " + trans.get("RenameConfigDialog.lbl.infoManufacturers");
StyledLabel info = new StyledLabel(text, -1);
+ trans.get("RenameConfigDialog.lbl.infoManufacturers")
+ trans.get("RenameConfigDialog.lbl.infoCombination");
StyledLabel info = new StyledLabel(text, -2);
info.setFontColor(Color.DARK_GRAY);
panel.add(info, "spanx, growx, wrap");