Merge branch 'unstable' into issue-1818
This commit is contained in:
commit
29020856cb
@ -470,6 +470,8 @@ SimulationExtension.javacode.name = Java code
|
||||
SimulationExtension.javacode.name.none = none
|
||||
SimulationExtension.javacode.desc = Add a custom SimulationListener to the simulation
|
||||
SimulationExtension.javacode.className = Fully-qualified Java class name:
|
||||
SimulationExtension.javacode.classnotfound = Could not find class
|
||||
SimulationExtension.javacode.couldnotinstantiate = <html>Could not instantiate class %s.<br>Does it have a zero-argument, or @Inject constructor?</html>
|
||||
|
||||
SimulationExtension.scripting.name = {language} script
|
||||
SimulationExtension.scripting.desc = Extend OpenRocket simulations by custom scripts.
|
||||
@ -552,6 +554,7 @@ SimuRunDlg.msg.errorOccurred = An error occurred during the simulation:
|
||||
|
||||
BasicEventSimulationEngine.error.noMotorsDefined = No motors defined in the simulation.
|
||||
BasicEventSimulationEngine.error.earlyMotorBurnout = Motor burnout without liftoff.
|
||||
BasicEventSimulationEngine.error.noConfiguredIgnition = No motors configured to ignite at liftoff
|
||||
BasicEventSimulationEngine.error.noIgnition = No motors ignited.
|
||||
BasicEventSimulationEngine.error.NaNResult = Simulation resulted in not-a-number (NaN) value, please report a bug.
|
||||
|
||||
@ -1028,6 +1031,8 @@ FreeformFinSetConfig.lbl.clickDrag = Click+drag: Add and move points
|
||||
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Delete point
|
||||
FreeformFinSetConfig.lbl.scaleFin = Scale Fin
|
||||
FreeformFinSetConfig.lbl.exportCSV = Export CSV
|
||||
FreeformFinSetConfig.lbl.deletePoint = Delete point
|
||||
FreeformFinSetConfig.lbl.insertPoint = Insert point
|
||||
|
||||
!TubeFinSetConfig
|
||||
TubeFinSetCfg.lbl.Nbroffins = Number of fins:
|
||||
@ -1827,7 +1832,7 @@ PlotConfiguration.Groundtrack = Ground track
|
||||
! Warning
|
||||
Warning.LargeAOA.str1 = Large angle of attack encountered.
|
||||
Warning.LargeAOA.str2 = Large angle of attack encountered (
|
||||
Warning.DISCONTINUITY = Discontinuity in rocket body diameter.
|
||||
Warning.DISCONTINUITY = Discontinuity in rocket body diameter
|
||||
Warning.THICK_FIN = Thick fins may not be modeled accurately.
|
||||
Warning.JAGGED_EDGED_FIN = Jagged-edged fin predictions may be inaccurate.
|
||||
Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation
|
||||
@ -1844,6 +1849,7 @@ Warning.ZERO_LENGTH_BODY = Zero length bodies may not result in accurate simulat
|
||||
Warning.ZERO_RADIUS_BODY = Zero length bodies may not result in accurate simulations.
|
||||
Warning.TUBE_SEPARATION = Space between tube fins may not result in accurate simulations.
|
||||
Warning.TUBE_OVERLAP = Overlapping tube fins may not result in accurate simulations.
|
||||
Warning.EMPTY_BRANCH = Simulation branch contains no data
|
||||
Warning.SEPARATION_ORDER = Stages separated in an unreasonable order
|
||||
|
||||
! Scale dialog
|
||||
|
@ -1315,7 +1315,7 @@ PlotConfiguration.Simulationtime = Simulacn
|
||||
! Warning
|
||||
Warning.LargeAOA.str1 = Velký úhel nábehu.
|
||||
Warning.LargeAOA.str2 = Velký úhel nábehu (
|
||||
Warning.DISCONTINUITY = Nespojitost v prumeru tela rakety.
|
||||
Warning.DISCONTINUITY = Nespojitost v prumeru tela rakety
|
||||
Warning.THICK_FIN = Tlou\u0161tka stabilizátoru se nemu\u017Ee modelovat presne.
|
||||
Warning.JAGGED_EDGED_FIN = Zubaté hrany stabilizátoru mohou být vypocteny nepresne.
|
||||
Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation
|
||||
|
@ -1225,7 +1225,7 @@ TrapezoidFinSetCfg.tab.Generalproperties = Propiedades generales
|
||||
! TubeCoupler
|
||||
TubeCoupler.TubeCoupler = Acoplador
|
||||
|
||||
Warning.DISCONTINUITY = Discontinuidad en el di\u00e1metro del fuselaje.
|
||||
Warning.DISCONTINUITY = Discontinuidad en el di\u00e1metro del fuselaje
|
||||
Warning.FILE_INVALID_PARAMETER = Par\u00e1metro encontrado no v\u00e1lido, ignorado.
|
||||
Warning.JAGGED_EDGED_FIN = El perfil afilado de las aletas puede ser inexacto.
|
||||
Warning.LISTENERS_AFFECTED = Las Extensiones se ejecutaron con la simulaci\u00f3n del vuelo
|
||||
|
@ -1219,7 +1219,7 @@ TrapezoidFinSetCfg.tab.Generalproperties = Propri\u00E9t\u00E9s g\u00E9n\u00E9ra
|
||||
! TubeCoupler
|
||||
TubeCoupler.TubeCoupler = Coupleur de tube
|
||||
|
||||
Warning.DISCONTINUITY = Discontinuit\u00E9 dans le diam\u00E8tre du corps de la fus\u00E9e.
|
||||
Warning.DISCONTINUITY = Discontinuit\u00E9 dans le diam\u00E8tre du corps de la fus\u00E9e
|
||||
Warning.FILE_INVALID_PARAMETER = Param\u00E8tre invalide rencontr\u00E9, ignorer.
|
||||
Warning.JAGGED_EDGED_FIN = Des ailerons aux bords irr\u00E9guliers ne seront pas mod\u00E9lis\u00E9s correctement.
|
||||
Warning.LISTENERS_AFFECTED = Les \u00E9couteurs ont modifi\u00E9 la simulation de vol
|
||||
|
@ -1379,7 +1379,7 @@ PlotConfiguration.Simulationtime = Intervallo temporale della simulazione e temp
|
||||
! Warning
|
||||
Warning.LargeAOA.str1 = Incontrato grande angolo d'attacco.
|
||||
Warning.LargeAOA.str2 = Incontrato grande angolo d'attacco (
|
||||
Warning.DISCONTINUITY = Discontinuita' nel diametro del tubo del corpo.
|
||||
Warning.DISCONTINUITY = Discontinuita' nel diametro del tubo del corpo
|
||||
Warning.THICK_FIN = Le pinne sottili potrebbero non essere modellate in modo accurato.
|
||||
Warning.JAGGED_EDGED_FIN = Jagged-edged fin Le predizioni per le pinne potrebbero non essere accurate.
|
||||
Warning.LISTENERS_AFFECTED = Gli osservatori possono modificare le condizioni di simulazione
|
||||
|
@ -1688,7 +1688,7 @@ PlotConfiguration.Groundtrack = Grondspoor
|
||||
! Warning
|
||||
Warning.LargeAOA.str1 = Grote invalshoek aangetroffen.
|
||||
Warning.LargeAOA.str2 = Grote invalshoek aangetroffen (
|
||||
Warning.DISCONTINUITY = Discontinuïteit in raketromp diameter.
|
||||
Warning.DISCONTINUITY = Discontinuïteit in raketromp diameter
|
||||
Warning.THICK_FIN = Dikke vinnen worden mogelijk niet nauwkeurig gemodelleerd.
|
||||
Warning.JAGGED_EDGED_FIN = De voorspellingen van gekartelde vinnen kunnen onnauwkeurig zijn.
|
||||
Warning.LISTENERS_AFFECTED = Luisteraars veranderden de vluchtsimulatie
|
||||
|
@ -1319,7 +1319,7 @@ update.dlg.latestVersion = Korzystasz z najnowszej wersji OpenRocket: %s.
|
||||
! Warning
|
||||
Warning.LargeAOA.str1 = Wyst\u0105pi\u0142 du\u017Cy k\u0105t natarcia.
|
||||
Warning.LargeAOA.str2 = Wyst\u0105pi\u0142 du\u017Cy k\u0105t natarcia (
|
||||
Warning.DISCONTINUITY = Nieci\u0105g\u0142o\u015B\u0107 \u015Brednicy rakiety.
|
||||
Warning.DISCONTINUITY = Nieci\u0105g\u0142o\u015B\u0107 \u015Brednicy rakiety
|
||||
Warning.THICK_FIN = Grube stateczniki mog\u0105 nie by\u0107 modelowane dok\u0142adnie.
|
||||
Warning.JAGGED_EDGED_FIN = Stateczniki o nieregularnych kraw\u0119dziach mog\u0105 zmniejszy\u0107 dok\u0142adno\u015B\u0107 prognoz.
|
||||
Warning.LISTENERS_AFFECTED = Detektory zmodyfikowa\u0142y symulacj\u0119 lotu
|
||||
|
@ -1182,7 +1182,7 @@ TrapezoidFinSetCfg.tab.Generalproperties = Propriedades gerais
|
||||
# TubeCoupler
|
||||
TubeCoupler.TubeCoupler = Acoplador de tubo
|
||||
|
||||
Warning.DISCONTINUITY = Descontinuidade no di\u00e2metro do corpo do foguete.
|
||||
Warning.DISCONTINUITY = Descontinuidade no di\u00e2metro do corpo do foguete
|
||||
Warning.FILE_INVALID_PARAMETER = Par\u00e2metro inv\u00e1lido encontrado, ignorando.
|
||||
Warning.JAGGED_EDGED_FIN = Previs\u00f5es com aletas de bordo irregular podem ser imprecisos.
|
||||
Warning.LISTENERS_AFFECTED = Observador modificou a simula\u00e7\u00e3o de voo
|
||||
|
@ -1535,7 +1535,7 @@ PlotConfiguration.Simulationtime = Simulation time step and computation time
|
||||
! Warning
|
||||
Warning.LargeAOA.str1 = Large angle of attack encountered.
|
||||
Warning.LargeAOA.str2 = Large angle of attack encountered (
|
||||
Warning.DISCONTINUITY = Discontinuity in rocket body diameter.
|
||||
Warning.DISCONTINUITY = Discontinuity in rocket body diameter
|
||||
Warning.THICK_FIN = Thick fins may not be modeled accurately.
|
||||
Warning.JAGGED_EDGED_FIN = Jagged-edged fin predictions may be inaccurate.
|
||||
Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation
|
||||
|
@ -1299,7 +1299,7 @@ TubeFinSetCfg.lbl.Outerdiam = \u5916\u76F4\u5F84:
|
||||
TubeFinSetCfg.lbl.Thickness = \u539A\u5EA6:
|
||||
TubeFinSetCfg.lbl.ttip.Finrotation = \u7A33\u5B9A\u7FFC\u7EC4\u5408\u91CC\u7B2C\u4E00\u7247\u7684\u89D2\u5EA6
|
||||
|
||||
Warning.DISCONTINUITY = \u7BAD\u4F53\u76F4\u5F84\u4E0D\u8FDE\u7EED.
|
||||
Warning.DISCONTINUITY = \u7BAD\u4F53\u76F4\u5F84\u4E0D\u8FDE\u7EED
|
||||
Warning.FILE_INVALID_PARAMETER = \u65E0\u6548\u53C2\u6570, \u5FFD\u7565.
|
||||
Warning.JAGGED_EDGED_FIN = \u952F\u9F7F\u7FFC\u9884\u6D4B\u53EF\u80FD\u4E0D\u51C6\u786E.
|
||||
Warning.LISTENERS_AFFECTED = \u76D1\u542C\u5668\u4FEE\u6539\u4E86\u98DE\u884C\u4EFF\u771F
|
||||
|
@ -67,6 +67,9 @@ public interface AerodynamicCalculator extends Monitorable {
|
||||
* @return a new, independent instance of this aerodynamic calculator type
|
||||
*/
|
||||
public AerodynamicCalculator newInstance();
|
||||
|
||||
public boolean isContinuous(FlightConfiguration configuration, final Rocket rkt);
|
||||
|
||||
/**
|
||||
* Test component assembly for continuity (esp. diameter), and post any needed warnings
|
||||
*/
|
||||
public void testIsContinuous(FlightConfiguration configuration, final RocketComponent component, WarningSet warnings);
|
||||
}
|
||||
|
@ -251,10 +251,8 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
|
||||
|
||||
if (calcMap == null)
|
||||
buildCalcMap(configuration);
|
||||
|
||||
if (!isContinuous(configuration, configuration.getRocket())){
|
||||
warnings.add( Warning.DIAMETER_DISCONTINUITY);
|
||||
}
|
||||
|
||||
testIsContinuous(configuration, configuration.getRocket(), warnings);
|
||||
|
||||
final InstanceMap imap = configuration.getActiveInstances();
|
||||
|
||||
@ -276,13 +274,9 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
|
||||
|
||||
return assemblyForces;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isContinuous(FlightConfiguration configuration, final Rocket rkt){
|
||||
return testIsContinuous(configuration, rkt);
|
||||
}
|
||||
|
||||
private boolean testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot ){
|
||||
public void testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot, WarningSet warnings ){
|
||||
Queue<RocketComponent> queue = new LinkedList<>();
|
||||
for (RocketComponent child : treeRoot.getChildren()) {
|
||||
// Ignore inactive stages
|
||||
@ -292,9 +286,8 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
|
||||
queue.add(child);
|
||||
}
|
||||
|
||||
boolean isContinuous = true;
|
||||
SymmetricComponent prevComp = null;
|
||||
while((isContinuous)&&( null != queue.peek())){
|
||||
while(null != queue.peek()) {
|
||||
RocketComponent comp = queue.poll();
|
||||
if( comp instanceof SymmetricComponent ){
|
||||
for (RocketComponent child : comp.getChildren()) {
|
||||
@ -313,7 +306,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
|
||||
|
||||
// Check for radius discontinuity
|
||||
if ( !MathUtil.equals(sym.getForeRadius(), prevComp.getAftRadius())) {
|
||||
isContinuous = false;
|
||||
warnings.add( Warning.DIAMETER_DISCONTINUITY, sym + ", " + prevComp);
|
||||
}
|
||||
|
||||
// double x = component.toAbsolute(Coordinate.NUL)[0].x;
|
||||
@ -327,11 +320,10 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
|
||||
|
||||
prevComp = sym;
|
||||
}else if( comp instanceof ComponentAssembly ){
|
||||
isContinuous &= testIsContinuous(configuration, comp);
|
||||
testIsContinuous(configuration, comp, warnings);
|
||||
}
|
||||
|
||||
}
|
||||
return isContinuous;
|
||||
}
|
||||
|
||||
|
||||
|
@ -18,7 +18,6 @@ public abstract class Warning {
|
||||
return new Warning.Other(text);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return <code>true</code> if the <code>other</code> warning should replace
|
||||
* this warning. The method should return <code>true</code> if the other
|
||||
@ -398,4 +397,6 @@ public abstract class Warning {
|
||||
public static final Warning TUBE_OVERLAP = new Other(trans.get("Warning.TUBE_OVERLAP"));
|
||||
|
||||
public static final Warning SEPARATION_ORDER = new Other(trans.get("Warning.SEPARATION_ORDER"));
|
||||
|
||||
public static final Warning EMPTY_BRANCH = new Other(trans.get("Warning.EMPTY_BRANCH"));
|
||||
}
|
||||
|
@ -67,7 +67,17 @@ public class WarningSet extends AbstractSet<Warning> implements Cloneable, Monit
|
||||
mutable.check();
|
||||
return add(Warning.fromString(s));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a <code>Warning</code> of the specified type with the specified discriminator to the
|
||||
* set.
|
||||
* @param w the warning
|
||||
* @param d the extra discriminator
|
||||
*
|
||||
*/
|
||||
public boolean add (Warning w, String d) {
|
||||
return this.add(w.toString() + ": " + d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Warning> iterator() {
|
||||
|
@ -68,7 +68,10 @@ class ComponentPresetSetter implements Setter {
|
||||
if (digest != null && !matchingPreset.getDigest().equals(digest)) {
|
||||
warnings.add(Warning.fromString("ComponentPreset for component " + c.getName() + " has wrong digest"));
|
||||
}
|
||||
|
||||
|
||||
// The preset loader can override the component name, so first store it and then apply it again
|
||||
String componentName = c.getName();
|
||||
setMethod.invoke(c, matchingPreset);
|
||||
c.setName(componentName);
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
* Maximum number of root points in the root geometry.
|
||||
*/
|
||||
private static final int MAX_ROOT_DIVISIONS = 100;
|
||||
private static final int MAX_ROOT_DIVISIONS_LOW_RES = 15;
|
||||
private static final int MAX_ROOT_DIVISIONS_LOW_RES = MAX_ROOT_DIVISIONS / 5;
|
||||
|
||||
public void setOverrideMass() {
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.sf.openrocket.simulation;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -8,6 +9,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sf.openrocket.aerodynamics.Warning;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.motor.IgnitionEvent;
|
||||
import net.sf.openrocket.motor.MotorConfiguration;
|
||||
import net.sf.openrocket.motor.MotorConfigurationId;
|
||||
import net.sf.openrocket.rocketcomponent.AxialStage;
|
||||
@ -68,11 +70,25 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
FlightConfiguration origConfig = simulationConditions.getRocket().getFlightConfiguration(this.fcid);
|
||||
FlightConfiguration simulationConfig = origConfig.clone();
|
||||
simulationConfig.copyStages(origConfig); // Clone the stage activation configuration
|
||||
if ( ! simulationConfig.hasMotors() ) {
|
||||
throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined"));
|
||||
}
|
||||
|
||||
currentStatus = new SimulationStatus(simulationConfig, simulationConditions);
|
||||
|
||||
// Sanity checks on design and configuration
|
||||
|
||||
// Problems that keep us from simulating at all
|
||||
|
||||
// No motors in configuration
|
||||
if (!simulationConfig.hasMotors() ) {
|
||||
throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined"));
|
||||
}
|
||||
|
||||
// Problems that let us simulate, but result is likely bad
|
||||
|
||||
// No recovery device
|
||||
if (!simulationConfig.hasRecoveryDevice()) {
|
||||
currentStatus.getWarnings().add(Warning.NO_RECOVERY_DEVICE);
|
||||
}
|
||||
|
||||
currentStatus.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket()));
|
||||
{
|
||||
// main simulation branch
|
||||
@ -97,6 +113,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
dataBranch.getBranchName(),
|
||||
currentStatus.getSimulationTime(),
|
||||
dataBranch.getLast(FlightDataType.TYPE_TIME)));
|
||||
|
||||
|
||||
// Did the branch generate any data?
|
||||
if (dataBranch.getLength() == 0) {
|
||||
flightData.getWarningSet().add(Warning.EMPTY_BRANCH, dataBranch.getBranchName());
|
||||
}
|
||||
}while( ! toSimulate.isEmpty());
|
||||
|
||||
SimulationListenerHelper.fireEndSimulation(currentStatus, null);
|
||||
@ -108,7 +130,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
return flightData;
|
||||
}
|
||||
|
||||
private FlightDataBranch simulateLoop() {
|
||||
private FlightDataBranch simulateLoop() throws SimulationException {
|
||||
|
||||
// Initialize the simulation. We'll use the flight stepper unless we're already on the ground
|
||||
if (currentStatus.isLanded())
|
||||
@ -240,9 +262,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
|
||||
} catch (SimulationException e) {
|
||||
SimulationListenerHelper.fireEndSimulation(currentStatus, e);
|
||||
|
||||
// Add FlightEvent for Abort.
|
||||
currentStatus.getFlightData().addEvent(new FlightEvent(FlightEvent.Type.EXCEPTION, currentStatus.getSimulationTime(), currentStatus.getConfiguration().getRocket(), e.getLocalizedMessage()));
|
||||
currentStatus.getWarnings().add(e.getLocalizedMessage());
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
return currentStatus.getFlightData();
|
||||
@ -341,11 +365,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
event.getTime() + Math.max(0.001, deployConfig.getDeployDelay()), c));
|
||||
}
|
||||
}
|
||||
|
||||
// Add a warning if there is no recovery device defined.
|
||||
if (!currentStatus.getConfiguration().hasRecoveryDevice()) {
|
||||
currentStatus.getWarnings().add(Warning.NO_RECOVERY_DEVICE);
|
||||
}
|
||||
|
||||
// Handle event
|
||||
log.trace("Handling event " + event);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.sf.openrocket.simulation.extension.impl;
|
||||
|
||||
import com.google.inject.ConfigurationException;
|
||||
import net.sf.openrocket.simulation.SimulationConditions;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
|
||||
@ -23,11 +24,15 @@ public class JavaCode extends AbstractSimulationExtension {
|
||||
if (!SimulationListener.class.isAssignableFrom(clazz)) {
|
||||
throw new SimulationException("Class " + className + " does not implement SimulationListener");
|
||||
}
|
||||
SimulationListener listener = (SimulationListener) injector.getInstance(clazz);
|
||||
conditions.getSimulationListenerList().add(listener);
|
||||
try {
|
||||
SimulationListener listener = (SimulationListener) injector.getInstance(clazz);
|
||||
conditions.getSimulationListenerList().add(listener);
|
||||
} catch (ConfigurationException e) {
|
||||
throw new SimulationException(String.format(trans.get("SimulationExtension.javacode.couldnotinstantiate"), className), e);
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new SimulationException("Could not find class " + className);
|
||||
throw new SimulationException(trans.get("SimulationExtension.javacode.classnotfound") + " " + className);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,11 @@ public class StopSimulationListener extends AbstractSimulationListener {
|
||||
|
||||
private long startTime = -1;
|
||||
private long time = -1;
|
||||
|
||||
|
||||
public StopSimulationListener() {
|
||||
this(0, 0);
|
||||
}
|
||||
|
||||
public StopSimulationListener(double t, int n) {
|
||||
stopTime = t;
|
||||
stopStep = n;
|
||||
|
@ -18,6 +18,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.util.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
@ -670,8 +671,8 @@ public class UnitGroup {
|
||||
if (!matcher.matches()) {
|
||||
throw new NumberFormatException("string did not match required pattern");
|
||||
}
|
||||
|
||||
double value = Double.parseDouble(matcher.group(1));
|
||||
|
||||
double value = StringUtil.convertToDouble(matcher.group(1));
|
||||
String unit = matcher.group(2).trim();
|
||||
|
||||
if (unit.equals("")) {
|
||||
|
@ -19,5 +19,24 @@ public class StringUtil {
|
||||
}
|
||||
return "".equals(s.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to a double, but with a more robust locale handling.
|
||||
* Some systems use a comma as a decimal separator, some a dot. This method
|
||||
* should work for both cases
|
||||
* @param input string to convert
|
||||
* @return double converted from string
|
||||
* @throws NumberFormatException if the string cannot be parsed.
|
||||
*/
|
||||
public static double convertToDouble(String input) {
|
||||
input = input.replace(',', '.');
|
||||
int decimalSeparator = input.lastIndexOf('.');
|
||||
|
||||
if (decimalSeparator > -1) {
|
||||
input = input.substring(0, decimalSeparator).replace(".", "") + input.substring(decimalSeparator);
|
||||
}
|
||||
|
||||
return Double.parseDouble(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -288,8 +288,10 @@ public class BarrowmanCalculatorTest {
|
||||
Rocket rocket = TestRockets.makeEstesAlphaIII();
|
||||
AerodynamicCalculator calc = new BarrowmanCalculator();
|
||||
FlightConfiguration configuration = rocket.getSelectedConfiguration();
|
||||
WarningSet warnings = new WarningSet();
|
||||
|
||||
assertTrue("Estes Alpha III should be continuous: ", calc.isContinuous(configuration, rocket));
|
||||
calc.testIsContinuous(configuration, rocket, warnings);
|
||||
assertTrue("Estes Alpha III should be continuous: ", warnings.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -297,8 +299,10 @@ public class BarrowmanCalculatorTest {
|
||||
Rocket rocket = TestRockets.makeFalcon9Heavy();
|
||||
AerodynamicCalculator calc = new BarrowmanCalculator();
|
||||
FlightConfiguration configuration = rocket.getSelectedConfiguration();
|
||||
WarningSet warnings = new WarningSet();
|
||||
|
||||
assertTrue("F9H should be continuous: ", calc.isContinuous(configuration, rocket));
|
||||
calc.testIsContinuous(configuration, rocket, warnings);
|
||||
assertTrue("F9H should be continuous: ", warnings.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -306,6 +310,7 @@ public class BarrowmanCalculatorTest {
|
||||
Rocket rocket = TestRockets.makeEstesAlphaIII();
|
||||
AerodynamicCalculator calc = new BarrowmanCalculator();
|
||||
FlightConfiguration configuration = rocket.getSelectedConfiguration();
|
||||
WarningSet warnings = new WarningSet();
|
||||
|
||||
NoseCone nose = (NoseCone)rocket.getChild(0).getChild(0);
|
||||
BodyTube body = (BodyTube)rocket.getChild(0).getChild(1);
|
||||
@ -313,8 +318,9 @@ public class BarrowmanCalculatorTest {
|
||||
nose.setAftRadius(0.015);
|
||||
body.setOuterRadius( 0.012 );
|
||||
body.setName( body.getName()+" << discontinuous");
|
||||
|
||||
assertFalse(" Estes Alpha III has an undetected discontinuity:", calc.isContinuous(configuration, rocket));
|
||||
|
||||
calc.testIsContinuous(configuration, rocket, warnings);
|
||||
assertFalse(" Estes Alpha III has an undetected discontinuity:", warnings.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -322,6 +328,7 @@ public class BarrowmanCalculatorTest {
|
||||
Rocket rocket = TestRockets.makeFalcon9Heavy();
|
||||
AerodynamicCalculator calc = new BarrowmanCalculator();
|
||||
FlightConfiguration configuration = rocket.getSelectedConfiguration();
|
||||
WarningSet warnings = new WarningSet();
|
||||
|
||||
final AxialStage coreStage = (AxialStage)rocket.getChild(1);
|
||||
final ParallelStage booster = (ParallelStage)coreStage.getChild(0).getChild(0);
|
||||
@ -333,7 +340,8 @@ public class BarrowmanCalculatorTest {
|
||||
body.setOuterRadius( 0.012 );
|
||||
body.setName( body.getName()+" << discontinuous");
|
||||
|
||||
assertFalse(" Missed discontinuity in Falcon 9 Heavy:", calc.isContinuous(configuration, rocket));
|
||||
calc.testIsContinuous(configuration, rocket, warnings);
|
||||
assertFalse(" Missed discontinuity in Falcon 9 Heavy:" , warnings.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -4,6 +4,7 @@ import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* A class that tests
|
||||
@ -22,4 +23,25 @@ public class StringUtilTest {
|
||||
assertFalse(StringUtil.isEmpty("A"));
|
||||
assertFalse(StringUtil.isEmpty(" . "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToDouble() {
|
||||
assertEquals(0.2, StringUtil.convertToDouble(".2"), MathUtil.EPSILON);
|
||||
assertEquals(0.2, StringUtil.convertToDouble(",2"), MathUtil.EPSILON);
|
||||
assertEquals(1, StringUtil.convertToDouble("1,"), MathUtil.EPSILON);
|
||||
assertEquals(2, StringUtil.convertToDouble("2."), MathUtil.EPSILON);
|
||||
assertEquals(1, StringUtil.convertToDouble("1"), MathUtil.EPSILON);
|
||||
assertEquals(1.52, StringUtil.convertToDouble("1.52"), MathUtil.EPSILON);
|
||||
assertEquals(1.52, StringUtil.convertToDouble("1,52"), MathUtil.EPSILON);
|
||||
assertEquals(1.5, StringUtil.convertToDouble("1.500"), MathUtil.EPSILON);
|
||||
assertEquals(1.5, StringUtil.convertToDouble("1,500"), MathUtil.EPSILON);
|
||||
assertEquals(1500.61, StringUtil.convertToDouble("1.500,61"), MathUtil.EPSILON);
|
||||
assertEquals(1500.61, StringUtil.convertToDouble("1,500.61"), MathUtil.EPSILON);
|
||||
assertEquals(1500.2, StringUtil.convertToDouble("1,500,200"), MathUtil.EPSILON);
|
||||
assertEquals(1500.2, StringUtil.convertToDouble("1.500.200"), MathUtil.EPSILON);
|
||||
assertEquals(1500200.23, StringUtil.convertToDouble("1500200.23"), MathUtil.EPSILON);
|
||||
assertEquals(1500200.23, StringUtil.convertToDouble("1500200,23"), MathUtil.EPSILON);
|
||||
assertEquals(1500200.23, StringUtil.convertToDouble("1,500,200.23"), MathUtil.EPSILON);
|
||||
assertEquals(1500200.23, StringUtil.convertToDouble("1.500.200,23"), MathUtil.EPSILON);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import java.util.ArrayList;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JDialog;
|
||||
@ -26,16 +27,21 @@ import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import net.sf.openrocket.gui.adaptors.CustomFocusTraversalPolicy;
|
||||
import net.sf.openrocket.gui.util.Icons;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -77,6 +83,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
|
||||
private JTable table = null;
|
||||
private FinPointTableModel tableModel = null;
|
||||
private JPopupMenu pm;
|
||||
|
||||
private int dragIndex = -1;
|
||||
private Point dragPoint = null;
|
||||
@ -84,6 +91,9 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
private FinPointFigure figure = null;
|
||||
private ScaleScrollPane figurePane = null;
|
||||
private ScaleSelector selector;
|
||||
|
||||
private FinPointAction insertFinPointAction;
|
||||
private FinPointAction deleteFinPointAction;
|
||||
|
||||
public FreeformFinSetConfig(OpenRocketDocument d, RocketComponent component, JDialog parent) {
|
||||
super(d, component, parent);
|
||||
@ -246,11 +256,31 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
}
|
||||
table.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent ev) {
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
int row = table.rowAtPoint(e.getPoint());
|
||||
|
||||
// Context menu on right-click
|
||||
if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() == 1) {
|
||||
// Select new row
|
||||
if (!table.isRowSelected(row)) {
|
||||
if (row >= 0 && row < table.getRowCount()) {
|
||||
table.setRowSelectionInterval(row, row);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
doPopup(e);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
figure.setSelectedIndex(table.getSelectedRow());
|
||||
figure.updateFigure();
|
||||
}
|
||||
|
||||
});
|
||||
JScrollPane tablePane = new JScrollPane(table);
|
||||
|
||||
@ -264,6 +294,21 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
dialog.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// Context menu for table
|
||||
insertFinPointAction = new InsertPointAction();
|
||||
deleteFinPointAction = new DeletePointAction();
|
||||
pm = new JPopupMenu();
|
||||
pm.add(insertFinPointAction);
|
||||
pm.add(deleteFinPointAction);
|
||||
|
||||
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
updateActionStates();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// panel.add(new JLabel("Coordinates:"), "aligny bottom, alignx 50%");
|
||||
// panel.add(new JLabel(" View:"), "wrap, aligny bottom");
|
||||
@ -303,10 +348,12 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
panel.setLayout(new MigLayout("fill, gap 5!","", "[nogrid, fill, sizegroup display, growprio 200]5![sizegroup text, growprio 5]5![sizegroup buttons, align top, growprio 5]0!"));
|
||||
|
||||
// first row: main display
|
||||
panel.add(tablePane, "width 100lp:100lp:, growy");
|
||||
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, tablePane, figurePane);
|
||||
splitPane.setResizeWeight(0.1);
|
||||
splitPane.setBorder(null);
|
||||
panel.add(splitPane, "width 300lp:500lp:, gap unrel, grow, height 100lp:250lp:, wrap");
|
||||
order.add(table);
|
||||
panel.add(figurePane, "width 200lp:400lp:, gap unrel, grow, height 100lp:250lp:, wrap");
|
||||
|
||||
|
||||
// row of text directly below figure
|
||||
panel.add(new StyledLabel(trans.get("lbl.doubleClick1")+" "+trans.get("FreeformFinSetConfig.lbl.doubleClick2"), -2), "spanx 3");
|
||||
panel.add(new StyledLabel(trans.get("FreeformFinSetConfig.lbl.clickDrag"), -2), "spanx 3");
|
||||
@ -414,6 +461,51 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
figurePane.revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new fin point between the currently selected point and the next point.
|
||||
* The coordinates of the new point will be the average of the two points.
|
||||
*/
|
||||
private void insertPoint() {
|
||||
int currentPointIdx = table.getSelectedRow();
|
||||
if (currentPointIdx == -1 || currentPointIdx >= table.getRowCount() - 1) {
|
||||
return;
|
||||
}
|
||||
final FreeformFinSet finSet = (FreeformFinSet) component;
|
||||
Coordinate currentPoint = finSet.getFinPoints()[currentPointIdx];
|
||||
Coordinate nextPoint = finSet.getFinPoints()[currentPointIdx + 1];
|
||||
Point2D.Double toAdd = new Point2D.Double((currentPoint.x + nextPoint.x) / 2, (currentPoint.y + nextPoint.y) / 2);
|
||||
finSet.addPoint(currentPointIdx + 1, toAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the currently selected fin point.
|
||||
*/
|
||||
private void deletePoint() {
|
||||
int currentPointIdx = table.getSelectedRow();
|
||||
if (currentPointIdx == -1) {
|
||||
return;
|
||||
}
|
||||
final FreeformFinSet finSet = (FreeformFinSet) component;
|
||||
try {
|
||||
finSet.removePoint(currentPointIdx);
|
||||
} catch (IllegalFinPointException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void doPopup(MouseEvent e) {
|
||||
pm.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
|
||||
private void updateActionStates() {
|
||||
if (insertFinPointAction == null) { // If one of the actions is null, the rest will be too
|
||||
return;
|
||||
}
|
||||
|
||||
insertFinPointAction.updateEnabledState();
|
||||
deleteFinPointAction.updateEnabledState();
|
||||
}
|
||||
|
||||
private class FinPointScrollPane extends ScaleScrollPane {
|
||||
|
||||
@ -693,4 +785,46 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class FinPointAction extends AbstractAction {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public abstract void updateEnabledState();
|
||||
}
|
||||
|
||||
private class InsertPointAction extends FinPointAction {
|
||||
public InsertPointAction() {
|
||||
putValue(NAME, trans.get("FreeformFinSetConfig.lbl.insertPoint"));
|
||||
this.putValue(SMALL_ICON, Icons.FILE_NEW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
insertPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEnabledState() {
|
||||
// You can't add to the last fin point
|
||||
setEnabled(table.getSelectedRow() < table.getRowCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private class DeletePointAction extends FinPointAction {
|
||||
public DeletePointAction() {
|
||||
putValue(NAME, trans.get("FreeformFinSetConfig.lbl.deletePoint"));
|
||||
this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
deletePoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEnabledState() {
|
||||
// You can't delete the first or last fin point
|
||||
setEnabled(table.getSelectedRow() > 0 && table.getSelectedRow() < table.getRowCount() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.gui.figure3d.geometry.Geometry.Surface;
|
||||
|
||||
public class FinRenderer {
|
||||
private GLUtessellator tobj = GLU.gluNewTess();
|
||||
private GLUtessellator tess = GLU.gluNewTess();
|
||||
|
||||
public void renderFinSet(final GL2 gl, FinSet finSet, Surface which) {
|
||||
|
||||
@ -50,7 +50,7 @@ public class FinRenderer {
|
||||
GLUtessellatorCallback cb = new GLUtessellatorCallbackAdapter() {
|
||||
@Override
|
||||
public void vertex(Object vertexData) {
|
||||
double d[] = (double[]) vertexData;
|
||||
double[] d = (double[]) vertexData;
|
||||
gl.glTexCoord2d(d[0], d[1]);
|
||||
gl.glVertex3dv(d, 0);
|
||||
}
|
||||
@ -64,69 +64,82 @@ public class FinRenderer {
|
||||
public void end() {
|
||||
gl.glEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void combine(double[] coords, Object[] data, float[] weight, Object[] outData) {
|
||||
double[] vertex = new double[3];
|
||||
vertex[0] = coords[0];
|
||||
vertex[1] = coords[1];
|
||||
vertex[2] = coords[2];
|
||||
outData[0] = vertex;
|
||||
}
|
||||
};
|
||||
|
||||
GLU.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, cb);
|
||||
GLU.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, cb);
|
||||
GLU.gluTessCallback(tobj, GLU.GLU_TESS_END, cb);
|
||||
|
||||
GLU.gluTessCallback(tess, GLU.GLU_TESS_VERTEX, cb);
|
||||
GLU.gluTessCallback(tess, GLU.GLU_TESS_BEGIN, cb);
|
||||
GLU.gluTessCallback(tess, GLU.GLU_TESS_END, cb);
|
||||
GLU.gluTessCallback(tess, GLU.GLU_TESS_COMBINE, cb);
|
||||
|
||||
// fin side: +z
|
||||
if (finSet.getSpan() > 0 && finSet.getLength() > 0 && which == Surface.INSIDE) { // Right side
|
||||
GLU.gluTessBeginPolygon(tobj, null);
|
||||
GLU.gluTessBeginContour(tobj);
|
||||
GLU.gluTessBeginPolygon(tess, null);
|
||||
GLU.gluTessBeginContour(tess);
|
||||
gl.glNormal3f(0, 0, 1);
|
||||
for (int i = finPoints.length - 1; i >= 0; i--) {
|
||||
Coordinate c = finPoints[i];
|
||||
double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(),
|
||||
c.z + finSet.getThickness() / 2.0};
|
||||
GLU.gluTessVertex(tobj, p, 0, p);
|
||||
GLU.gluTessVertex(tess, p, 0, p);
|
||||
}
|
||||
GLU.gluTessEndContour(tobj);
|
||||
GLU.gluTessEndPolygon(tobj);
|
||||
GLU.gluTessEndContour(tess);
|
||||
GLU.gluTessEndPolygon(tess);
|
||||
}
|
||||
// tab side: +z
|
||||
if (finSet.getTabHeight() > 0 && finSet.getTabLength() > 0 && which == Surface.INSIDE) { // Right side
|
||||
GLU.gluTessBeginPolygon(tobj, null);
|
||||
GLU.gluTessBeginContour(tobj);
|
||||
GLU.gluTessBeginPolygon(tess, null);
|
||||
GLU.gluTessBeginContour(tess);
|
||||
gl.glNormal3f(0, 0, 1);
|
||||
for (int i = tabPoints.length - 1; i >= 0; i--) {
|
||||
Coordinate c = tabPoints[i];
|
||||
double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(),
|
||||
c.z + finSet.getThickness() / 2.0};
|
||||
GLU.gluTessVertex(tobj, p, 0, p);
|
||||
GLU.gluTessVertex(tess, p, 0, p);
|
||||
}
|
||||
GLU.gluTessEndContour(tobj);
|
||||
GLU.gluTessEndPolygon(tobj);
|
||||
GLU.gluTessEndContour(tess);
|
||||
GLU.gluTessEndPolygon(tess);
|
||||
}
|
||||
|
||||
// fin side: -z
|
||||
if (finSet.getSpan() > 0 && finSet.getLength() > 0 && which == Surface.OUTSIDE) { // Left side
|
||||
GLU.gluTessBeginPolygon(tobj, null);
|
||||
GLU.gluTessBeginContour(tobj);
|
||||
GLU.gluTessBeginPolygon(tess, null);
|
||||
GLU.gluTessBeginContour(tess);
|
||||
gl.glNormal3f(0, 0, -1);
|
||||
for (Coordinate c : finPoints) {
|
||||
double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(),
|
||||
c.z - finSet.getThickness() / 2.0};
|
||||
GLU.gluTessVertex(tobj, p, 0, p);
|
||||
GLU.gluTessVertex(tess, p, 0, p);
|
||||
|
||||
}
|
||||
GLU.gluTessEndContour(tobj);
|
||||
GLU.gluTessEndPolygon(tobj);
|
||||
GLU.gluTessEndContour(tess);
|
||||
GLU.gluTessEndPolygon(tess);
|
||||
}
|
||||
// tab side: -z
|
||||
if (finSet.getTabHeight() > 0 && finSet.getTabLength() > 0 && which == Surface.OUTSIDE) { // Left side
|
||||
GLU.gluTessBeginPolygon(tobj, null);
|
||||
GLU.gluTessBeginContour(tobj);
|
||||
GLU.gluTessBeginPolygon(tess, null);
|
||||
GLU.gluTessBeginContour(tess);
|
||||
gl.glNormal3f(0, 0, -1);
|
||||
for (Coordinate c : tabPoints) {
|
||||
double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(),
|
||||
c.z - finSet.getThickness() / 2.0};
|
||||
GLU.gluTessVertex(tobj, p, 0, p);
|
||||
GLU.gluTessVertex(tess, p, 0, p);
|
||||
|
||||
}
|
||||
GLU.gluTessEndContour(tobj);
|
||||
GLU.gluTessEndPolygon(tobj);
|
||||
GLU.gluTessEndContour(tess);
|
||||
GLU.gluTessEndPolygon(tess);
|
||||
}
|
||||
|
||||
// delete tessellator after processing
|
||||
GLU.gluDeleteTess(tess);
|
||||
|
||||
// Fin strip around the edge
|
||||
if (finSet.getSpan() > 0 && finSet.getLength() > 0 && which == Surface.EDGES) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.sf.openrocket.gui.main;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
@ -20,7 +22,25 @@ import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import javax.swing.*;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.BevelBorder;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.tree.DefaultTreeSelectionModel;
|
||||
@ -259,11 +279,13 @@ public class BasicFrame extends JFrame {
|
||||
if( componentSelectionModel.isSelectionEmpty() ){
|
||||
final Rocket rocket = document.getRocket();
|
||||
if( rocket != null ) {
|
||||
final AxialStage topStage = (AxialStage) rocket.getChild(0);
|
||||
final RocketComponent topStage = rocket.getChild(0);
|
||||
if (topStage != null) {
|
||||
final TreePath selectionPath = new TreePath(topStage);
|
||||
componentSelectionModel.setSelectionPath(selectionPath);
|
||||
tree.setSelectionRow(1);
|
||||
// Don't select children components at startup (so override the default behavior with this new selection)
|
||||
rocketpanel.getFigure().setSelection(new RocketComponent[] { topStage });
|
||||
log.debug("... Setting Initial Selection: " + tree.getSelectionPath() );
|
||||
}
|
||||
}
|
||||
|
@ -69,36 +69,30 @@ public class DesignPanel extends JSplitPane {
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, SHORTCUT_KEY), null);
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, SHORTCUT_KEY), null);
|
||||
|
||||
// Visually select all child components of a stage/rocket/podset when it is selected
|
||||
// Highlight all child components of a stage/rocket/podset when it is selected
|
||||
tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(TreeSelectionEvent e) {
|
||||
if (tree == null || tree.getSelectionPaths() == null || tree.getSelectionPaths().length == 0
|
||||
|| parent.getRocketPanel() == null) return;
|
||||
|
||||
// Get all the components that need to be selected = currently selected components + children of stages/boosters/podsets
|
||||
List<RocketComponent> children = new ArrayList<>(Arrays.asList(parent.getRocketPanel().getFigure().getSelection()));
|
||||
for (TreePath p : tree.getSelectionPaths()) {
|
||||
if (p != null) {
|
||||
RocketComponent c = (RocketComponent) p.getLastPathComponent();
|
||||
if (c instanceof AxialStage || c instanceof Rocket || c instanceof PodSet) {
|
||||
Iterator<RocketComponent> iter = c.iterator(false);
|
||||
while (iter.hasNext()) {
|
||||
RocketComponent child = iter.next();
|
||||
children.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select all the child components
|
||||
if (parent.getRocketPanel().getFigure() != null && parent.getRocketPanel().getFigure3d() != null) {
|
||||
parent.getRocketPanel().getFigure().setSelection(children.toArray(new RocketComponent[0]));
|
||||
parent.getRocketPanel().getFigure3d().setSelection(children.toArray(new RocketComponent[0]));
|
||||
}
|
||||
highlightAssemblyChildren(tree, parent);
|
||||
}
|
||||
});
|
||||
|
||||
// Add a mouse listener for when the sustainer is selected at startup, to ensure that its children are highlighted.
|
||||
// This is necessary because we force the children to not be highlighted when the tree is first created, and
|
||||
// re-clicking the sustainer would not fire a change event in the tree (which normally highlights the children).
|
||||
MouseAdapter mouseAdapter = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (tree.getSelectionPath() != null &&
|
||||
tree.getSelectionPath().getLastPathComponent() == document.getRocket().getChild(0)) {
|
||||
highlightAssemblyChildren(tree, parent);
|
||||
}
|
||||
// Delete the listener again. We only need it at start-up, i.e. when the first click is registered.
|
||||
tree.removeMouseListener(this);
|
||||
}
|
||||
};
|
||||
tree.addMouseListener(mouseAdapter);
|
||||
|
||||
// Double-click opens config dialog
|
||||
MouseListener ml = new MouseAdapter() {
|
||||
@Override
|
||||
@ -234,6 +228,37 @@ public class DesignPanel extends JSplitPane {
|
||||
this.setRightComponent(panel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight all child components of a stage/rocket/podset when it is selected
|
||||
* @param tree the tree in which the component selection took place
|
||||
* @param parent the parent frame to highlight the components in
|
||||
*/
|
||||
private static void highlightAssemblyChildren(ComponentTree tree, BasicFrame parent) {
|
||||
if (tree == null || tree.getSelectionPaths() == null || tree.getSelectionPaths().length == 0
|
||||
|| parent.getRocketPanel() == null) return;
|
||||
|
||||
// Get all the components that need to be selected = currently selected components + children of stages/boosters/podsets
|
||||
List<RocketComponent> children = new ArrayList<>(Arrays.asList(parent.getRocketPanel().getFigure().getSelection()));
|
||||
for (TreePath p : tree.getSelectionPaths()) {
|
||||
if (p != null) {
|
||||
RocketComponent c = (RocketComponent) p.getLastPathComponent();
|
||||
if (c instanceof AxialStage || c instanceof Rocket || c instanceof PodSet) {
|
||||
Iterator<RocketComponent> iter = c.iterator(false);
|
||||
while (iter.hasNext()) {
|
||||
RocketComponent child = iter.next();
|
||||
children.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select all the child components
|
||||
if (parent.getRocketPanel().getFigure() != null && parent.getRocketPanel().getFigure3d() != null) {
|
||||
parent.getRocketPanel().getFigure().setSelection(children.toArray(new RocketComponent[0]));
|
||||
parent.getRocketPanel().getFigure3d().setSelection(children.toArray(new RocketComponent[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus on the component tree.
|
||||
*/
|
||||
|
@ -208,13 +208,10 @@ public class SimulationPanel extends JPanel {
|
||||
}
|
||||
// Show context menu
|
||||
else if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() == 1) {
|
||||
// Get the row that the right-click action happened on
|
||||
int r = simulationTable.rowAtPoint(e.getPoint());
|
||||
|
||||
// Select new row
|
||||
if (!simulationTable.isRowSelected(r)) {
|
||||
if (r >= 0 && r < simulationTable.getRowCount()) {
|
||||
simulationTable.setRowSelectionInterval(r, r);
|
||||
if (!simulationTable.isRowSelected(row)) {
|
||||
if (row >= 0 && row < simulationTable.getRowCount()) {
|
||||
simulationTable.setRowSelectionInterval(row, row);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import net.sf.openrocket.simulation.FlightDataType;
|
||||
import net.sf.openrocket.simulation.FlightEvent;
|
||||
import net.sf.openrocket.unit.Unit;
|
||||
import net.sf.openrocket.unit.UnitGroup;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.LinearInterpolator;
|
||||
|
||||
import net.sf.openrocket.utils.DecimalFormatter;
|
||||
|
@ -5,7 +5,6 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Point;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.InputEvent;
|
||||
@ -16,7 +15,6 @@ import java.util.EventListener;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
@ -209,12 +209,10 @@ public class SimulationEditDialog extends JDialog {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
copyChangesToAllSims();
|
||||
SimulationRunDialog.runSimulations(parentWindow, SimulationEditDialog.this.document, simulationList);
|
||||
refreshView();
|
||||
if (allowsPlotMode()) {
|
||||
SimulationRunDialog dialog = SimulationRunDialog.runSimulations(parentWindow, SimulationEditDialog.this.document, simulationList);
|
||||
if (allowsPlotMode() && dialog.isAllSimulationsSuccessful()) {
|
||||
refreshView();
|
||||
setPlotMode();
|
||||
} else {
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -237,6 +237,7 @@ class SimulationOptionsPanel extends JPanel {
|
||||
SwingSimulationExtensionConfigurator configurator = findConfigurator(e);
|
||||
if (configurator != null) {
|
||||
configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
|
||||
updateCurrentExtensions();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -281,6 +282,7 @@ class SimulationOptionsPanel extends JPanel {
|
||||
SwingSimulationExtensionConfigurator configurator = findConfigurator(e);
|
||||
if (configurator != null) {
|
||||
configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
|
||||
updateCurrentExtensions();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -23,6 +23,8 @@ import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JProgressBar;
|
||||
|
||||
import net.sf.openrocket.document.events.DocumentChangeEvent;
|
||||
import net.sf.openrocket.document.events.SimulationChangeEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -207,9 +209,12 @@ public class SimulationRunDialog extends JDialog {
|
||||
* the parent Window of the dialog to use.
|
||||
* @param simulations
|
||||
* the simulations to run.
|
||||
* @return the simulation run dialog instance.
|
||||
*/
|
||||
public static void runSimulations(Window parent, OpenRocketDocument document, Simulation... simulations) {
|
||||
new SimulationRunDialog(parent, document, simulations).setVisible(true);
|
||||
public static SimulationRunDialog runSimulations(Window parent, OpenRocketDocument document, Simulation... simulations) {
|
||||
SimulationRunDialog dialog = new SimulationRunDialog(parent, document, simulations);
|
||||
dialog.setVisible(true);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void updateProgress() {
|
||||
@ -257,6 +262,18 @@ public class SimulationRunDialog extends JDialog {
|
||||
+ u.toStringUnit(simulationMaxVelocity[index]) + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all the simulations ran successfully. Returns false if the simulations encountered
|
||||
* an exception, or were cancelled.
|
||||
*/
|
||||
public boolean isAllSimulationsSuccessful() {
|
||||
for (SimulationWorker w : simulationWorkers) {
|
||||
if (w.getThrowable() != null)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A SwingWorker that performs a flight simulation. It periodically updates
|
||||
* the simulation statuses of the parent class and calls updateProgress().
|
||||
@ -273,6 +290,7 @@ public class SimulationRunDialog extends JDialog {
|
||||
private volatile double apogeeAltitude;
|
||||
|
||||
private final CustomExpressionSimulationListener exprListener;
|
||||
private final OpenRocketDocument document;
|
||||
|
||||
/*
|
||||
* Keep track of current phase ("stage") of simulation
|
||||
@ -287,7 +305,8 @@ public class SimulationRunDialog extends JDialog {
|
||||
|
||||
public InteractiveSimulationWorker(OpenRocketDocument doc, Simulation sim, int index) {
|
||||
super(sim);
|
||||
List<CustomExpression> exprs = doc.getCustomExpressions();
|
||||
this.document = doc;
|
||||
List<CustomExpression> exprs = document.getCustomExpressions();
|
||||
exprListener = new CustomExpressionSimulationListener(exprs);
|
||||
this.index = index;
|
||||
|
||||
@ -389,6 +408,7 @@ public class SimulationRunDialog extends JDialog {
|
||||
log.debug("Simulation done");
|
||||
setSimulationProgress(1.0);
|
||||
updateProgress();
|
||||
document.fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,6 +70,17 @@ public abstract class SimulationWorker extends SwingWorker<FlightData, Simulatio
|
||||
protected SimulationListener[] getExtraListeners() {
|
||||
return new SimulationListener[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the throwable that caused the simulation to fail or cancel,
|
||||
* or null if the simulation ran successfully.
|
||||
*
|
||||
* @return throwable that caused the simulation to fail or cancel,
|
||||
* or null if the simulation ran successfully.
|
||||
*/
|
||||
public Throwable getThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -4,6 +4,8 @@ import java.awt.Dialog.ModalityType;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
@ -50,17 +52,21 @@ public abstract class AbstractSwingSimulationExtensionConfigurator<E extends Sim
|
||||
close.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
dialog.setVisible(false);
|
||||
close();
|
||||
}
|
||||
});
|
||||
panel.add(close, "right");
|
||||
|
||||
|
||||
dialog.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.add(panel);
|
||||
GUIUtil.setDisposableDialogOptions(dialog, close);
|
||||
dialog.setVisible(true);
|
||||
close();
|
||||
GUIUtil.setNullModels(dialog);
|
||||
dialog = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,10 +84,12 @@ public abstract class AbstractSwingSimulationExtensionConfigurator<E extends Sim
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the default dialog is closed. By default does nothing.
|
||||
* Called when the default dialog is closed. By default hides the dialog and cleans up the component models.
|
||||
*/
|
||||
protected void close() {
|
||||
|
||||
dialog.setVisible(false);
|
||||
GUIUtil.setNullModels(dialog);
|
||||
dialog = null;
|
||||
}
|
||||
|
||||
protected abstract JComponent getConfigurationComponent(E extension, Simulation simulation, JPanel panel);
|
||||
|
@ -8,22 +8,38 @@ import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import net.sf.openrocket.document.Simulation;
|
||||
import net.sf.openrocket.gui.components.StyledLabel;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.plugin.Plugin;
|
||||
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.util.Color;
|
||||
|
||||
@Plugin
|
||||
public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfigurator<JavaCode> {
|
||||
|
||||
private JavaCode extension;
|
||||
private JTextField classNameField;
|
||||
private StyledLabel errorMsg;
|
||||
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
public JavaCodeConfigurator() {
|
||||
super(JavaCode.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent getConfigurationComponent(final JavaCode extension, Simulation simulation, JPanel panel) {
|
||||
this.extension = extension;
|
||||
panel.add(new JLabel(trans.get("SimulationExtension.javacode.desc")), "wrap para");
|
||||
panel.add(new JLabel(trans.get("SimulationExtension.javacode.className")), "wrap rel");
|
||||
final JTextField textField = new JTextField(extension.getClassName());
|
||||
textField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
classNameField = new JTextField(extension.getClassName());
|
||||
panel.add(classNameField, "growx, wrap");
|
||||
this.errorMsg = new StyledLabel();
|
||||
errorMsg.setFontColor(Color.DARK_RED.toAWTColor());
|
||||
errorMsg.setVisible(false);
|
||||
panel.add(errorMsg, "growx, wrap");
|
||||
|
||||
classNameField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
update();
|
||||
}
|
||||
@ -37,11 +53,41 @@ public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfig
|
||||
}
|
||||
|
||||
public void update() {
|
||||
extension.setClassName(textField.getText());
|
||||
updateErrorMsg();
|
||||
}
|
||||
});
|
||||
panel.add(textField, "growx");
|
||||
updateErrorMsg();
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void updateErrorMsg() {
|
||||
if (this.errorMsg == null) {
|
||||
return;
|
||||
}
|
||||
// Display error message if the class name is invalid
|
||||
String text = classNameField.getText().trim();
|
||||
try {
|
||||
Class.forName(text);
|
||||
errorMsg.setVisible(false);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Don't display an error message for an empty field
|
||||
if (text.length() == 0) {
|
||||
errorMsg.setVisible(false);
|
||||
return;
|
||||
}
|
||||
errorMsg.setText(trans.get("SimulationExtension.javacode.classnotfound"));
|
||||
errorMsg.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void close() {
|
||||
if (this.extension != null && this.classNameField != null) {
|
||||
this.extension.setClassName(this.classNameField.getText().trim());
|
||||
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user