<separationdelay> elements to stage components (except sustainer).
1.5: Introduced with OpenRocket 12.xx. Added ComponentPresets.
- Added lowerstageseparation as recovery device deployment event.
\ No newline at end of file
+ Added lowerstageseparation as recovery device deployment event.
+
+1.6 (pre):
+ Added <datatypes> section for supporting datatypes other than
+ internal ones. Currently only supports datatypes from custom expressions.
\ No newline at end of file
! Custom expression panel
customExpressionPanel.but.NewExpression = New expression
+customExpressionPanel.but.ttip.NewExpression = Add a new custom expression
+customExpressionPanel.but.Import = Import
+customExpressionPanel.but.ttip.Import = Import custom expressions from another .ork file
customExpressionPanel.lbl.UpdateNote = You must run the simulation before data will be available for plotting.
customExpressionPanel.lbl.CalcNote = Expressions will be calculated in the order shown.
-customExpressionPanel.lbl.CustomExpressions = Custom Expressions :
+customExpressionPanel.lbl.CustomExpressions = Custom Expressions
customExpression.Units.but.ttip.Remove = Remove this expression
customExpression.Units.but.ttip.Edit = Edit this expression
customExpression.Units.but.ttip.MoveUp = Move expression up in calculation order
customExpression.Units.but.ttip.MoveDown = Move expression down in calculation order
+
! Custom expression builder window
ExpressionBuilderDialog.title = Expression Builder
ExpressionBuilderDialog.InsertVariable = Insert Variable
Operator.plus = Addition
Operator.minus = Subtraction
Operator.star = Multiplication
-Operator.div = Divison
+Operator.div = Division
Operator.mod = Modulo
Operator.pow = Exponentiation
Operator.abs = Absolute value
-Operator.ceil = Ceiling (next integer value
-Operator.floor = Floor (previous integer value
+Operator.ceil = Ceiling (next integer value)
+Operator.floor = Floor (previous integer value)
Operator.sqrt = Square root
Operator.cbrt = Cubic root
Operator.exp = Euler\'s number raised to the value (e^x)
Operator.asin = Arc sine
Operator.acos = Arc cosine
Operator.atan = Arc tangent
-Operator.hsin = Hyerbolic sine
+Operator.hsin = Hyperbolic sine
Operator.hcos = Hyperbolic cosine
Operator.htan = Hyperbolic tangent
+Operator.log10 = Base 10 logarithm
+Operator.round = Round to nearest integer value
+Operator.random = Random number between zero and given value
+Operator.expm1 = The same as exp(x)-1, but more accurate for small x
+Operator.mean = The arithmetic mean of a given range
+Operator.min = The minimum value in a given range
+Operator.max = The maximum value in a given range
+Operator.var = The variance of a given range
+Operator.stdev = The standard deviation of a given range
+Operator.rms = The root-mean-squared value of a given range
+Operator.lclip = Clips a value (1st parameter) to be no less than a given value (2nd parameter)
+Operator.uclip = Clips a value (1st parameter) to be no greater than a given value (2nd parameter)
+Operator.binf = Gives the fraction of values in a given range (1st parameter) inside a bin with given lower (2nd parameter) and upper (3rd parameter) bounds
+Operator.trapz = Integrates the given range using trapezoidal integration
+Operator.tnear = Find the time corresponding to the point in a range (1st parameter) nearest to a given value (2nd parameter)
! MotorPlot
MotorPlot.title.Motorplot = Motor plot
main.menu.analyze.componentAnalysis.desc = Analyze the rocket components separately
main.menu.analyze.optimization = Rocket optimization
main.menu.analyze.optimization.desc = General rocket design optimization
+main.menu.analyze.customExpressions = Custom expressions
+main.menu.analyze.customExpressions.desc = Define new flight data types by writing custom mathematical expressions
main.menu.help = Help
main.menu.help.desc = Information about OpenRocket
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
private final Configuration configuration;
private final ArrayList<Simulation> simulations = new ArrayList<Simulation>();
-
+ private ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
+
/*
* The undo/redo variables and mechanism are documented in doc/undo-redo-flow.*
}
-
+ public void addCustomExpression(CustomExpression expression){
+ if (customExpressions.contains(expression)){
+ log.user("Could not add custom expression "+expression.getName()+" to document as document alerady has a matching expression.");
+ } else {
+ customExpressions.add(expression);
+ }
+ }
+
+ public void removeCustomExpression(CustomExpression expression){
+ customExpressions.remove(expression);
+ }
+
+ public ArrayList<CustomExpression> getCustomExpressions(){
+ return customExpressions;
+ }
+
public Rocket getRocket() {
return rocket;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.simulation.BasicEventSimulationEngine;
-import net.sf.openrocket.simulation.CustomExpression;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.RK4SimulationStepper;
import net.sf.openrocket.simulation.SimulationConditions;
private SimulationOptions options;
private ArrayList<String> simulationListeners = new ArrayList<String>();
- private ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
return document;
}
- public void addCustomExpression(CustomExpression expression){
- this.status = Simulation.Status.OUTDATED;
- log.debug("Simulation must be run again to update custom expression.");
- customExpressions.add(expression);
- }
-
- public void removeCustomExpression(CustomExpression expression){
- customExpressions.remove(expression);
- }
-
- public ArrayList<CustomExpression> getCustomExpressions(){
- return customExpressions;
- }
-
-
/**
* Return the rocket associated with this simulation.
*
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
writeln("");
+ // Save custom expressions;
+ saveCustomDatatypes(document);
+
// Save all simulations
writeln("<simulations>");
indent++;
}
}
+ /*
+ * Save all the custom expressions
+ */
+ private void saveCustomDatatypes(OpenRocketDocument doc) throws IOException {
+
+ if (doc.getCustomExpressions().isEmpty())
+ return;
+
+ writeln("<datatypes>"); indent++;
+
+ for (CustomExpression exp : doc.getCustomExpressions()){
+ saveCustomExpressionDatatype(exp);
+ }
+
+ indent--; writeln("</datatypes>");
+ writeln("");
+ }
+ /*
+ * Save one custom expression datatype
+ */
+ private void saveCustomExpressionDatatype(CustomExpression exp) throws IOException {
+ // Write out custom expression
+
+ writeln("<type source=\"customexpression\">"); indent++;
+ writeln("<name>" + exp.getName() + "</name>");
+ writeln("<symbol>" + exp.getSymbol() + "</symbol>");
+ writeln("<unit unittype=\"auto\">" + exp.getUnit() + "</unit>"); // auto unit type means it will be determined from string
+ writeln("<expression>" + exp.getExpressionString() + "</expression>");
+ indent--; writeln("</type>");
+ }
@Override
public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
writeln("<simulator>RK4Simulator</simulator>");
writeln("<calculator>BarrowmanCalculator</calculator>");
- // Write out custom expressions
- if (!simulation.getCustomExpressions().isEmpty()){
- writeln("<customexpressions>"); indent++;
- for (CustomExpression expression : simulation.getCustomExpressions()){
- writeln("<expression>"); indent++;
- writeElement("name", expression.getName());
- writeElement("symbol", expression.getSymbol());
- writeElement("unit", expression.getUnit());
- writeElement("expressionstring", expression.getExpressionString());
- indent--; writeln("</expression>");
- }
- indent--; writeln("</customexpressions>");
- }
-
writeln("<conditions>"); indent++;
writeElement("configid", cond.getMotorConfigurationID());
sb.append("<databranch name=\"");
sb.append(escapeXML(branch.getBranchName()));
-
+ // Kevins version where typekeys are used
+ /*
sb.append("\" typekeys=\"");
for (int i = 0; i < types.length; i++) {
if (i > 0)
sb.append(",");
sb.append(escapeXML(types[i].getKey()));
}
+ */
+
sb.append("\" types=\"");
for (int i = 0; i < types.length; i++) {
if (i > 0)
import net.sf.openrocket.rocketcomponent.Transition;
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
private boolean rocketDefined = false;
private boolean simulationsDefined = false;
+ private boolean datatypesDefined = false;
public OpenRocketContentHandler(DocumentLoadingContext context) {
this.context = context;
this.doc = new OpenRocketDocument(rocket);
}
-
public OpenRocketDocument getDocument() {
if (!rocketDefined)
return null;
rocketDefined = true;
return new ComponentParameterHandler(rocket, context);
}
+
+ if (element.equals("datatypes")){
+ if (datatypesDefined) {
+ warnings.add(Warning.fromString("Multiple datatype blocks. Ignoring later ones."));
+ return null;
+ }
+ datatypesDefined = true;
+ return new DatatypeHandler(this, context);
+ }
if (element.equals("simulations")) {
if (simulationsDefined) {
+class DatatypeHandler extends AbstractElementHandler {
+ private final DocumentLoadingContext context;
+ private final OpenRocketContentHandler contentHandler;
+ private CustomExpressionHandler customExpressionHandler = null;
+
+ public DatatypeHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) {
+ this.context = context;
+ this.contentHandler = contentHandler;
+ }
+
+ @Override
+ public ElementHandler openElement(String element,
+ HashMap<String, String> attributes, WarningSet warnings)
+ throws SAXException {
+
+ if (element.equals("type") && attributes.get("source").equals("customexpression") ){
+ customExpressionHandler = new CustomExpressionHandler(contentHandler, context);
+ return customExpressionHandler;
+ }
+ else {
+ warnings.add(Warning.fromString("Unknown datatype " + element + " defined, ignoring"));
+ }
+
+ return this;
+ }
+
+ @Override
+ public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
+ attributes.remove("source");
+ super.closeElement(element, attributes, content, warnings);
+
+ if (customExpressionHandler != null){
+ contentHandler.getDocument().addCustomExpression(customExpressionHandler.currentExpression);
+ }
+
+ }
+
+}
+
+class CustomExpressionHandler extends AbstractElementHandler{
+ private final DocumentLoadingContext context;
+ private final OpenRocketContentHandler contentHandler;
+ public CustomExpression currentExpression;
+
+ public CustomExpressionHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) {
+ this.context = context;
+ this.contentHandler = contentHandler;
+ currentExpression = new CustomExpression(contentHandler.getDocument());
+
+ }
+
+ @Override
+ public ElementHandler openElement(String element,
+ HashMap<String, String> attributes, WarningSet warnings)
+ throws SAXException {
+
+ return this;
+ }
+
+ @Override
+ public void closeElement(String element, HashMap<String, String> attributes,
+ String content, WarningSet warnings) throws SAXException {
+
+ if (element.equals("type")) {
+ contentHandler.getDocument().addCustomExpression(currentExpression);
+ }
+
+ if (element.equals("name")) {
+ currentExpression.setName(content);
+ }
+
+ if (element.equals("symbol")) {
+ currentExpression.setSymbol(content);
+ }
+
+ if (element.equals("unit") && attributes.get("unittype").equals("auto")) {
+ currentExpression.setUnit(content);
+ }
+
+ if (element.equals("expression")){
+ currentExpression.setExpression(content);
+ }
+ }
+}
/**
* A handler that creates components from the corresponding elements. The control of the
private SimulationConditionsHandler conditionHandler;
private FlightDataHandler dataHandler;
- private CustomExpressionsHandler customExpressionsHandler;
-
- private ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
+
private final List<String> listeners = new ArrayList<String>();
public SingleSimulationHandler(OpenRocketDocument doc, DocumentLoadingContext context) {
this.context = context;
}
- public void setCustomExpressions(ArrayList<CustomExpression> expressions){
- this.customExpressions = expressions;
- }
-
- public ArrayList<CustomExpression> getCustomExpressions(){
- return customExpressions;
+ public OpenRocketDocument getDocument(){
+ return doc;
}
-
+
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes,
WarningSet warnings) {
if (element.equals("name") || element.equals("simulator") ||
element.equals("calculator") || element.equals("listener")) {
return PlainTextHandler.INSTANCE;
- } else if (element.equals("customexpressions")) {
- customExpressionsHandler = new CustomExpressionsHandler(this, context);
- return customExpressionsHandler;
} else if (element.equals("conditions")) {
conditionHandler = new SimulationConditionsHandler(doc.getRocket(), context);
return conditionHandler;
Simulation simulation = new Simulation(doc, doc.getRocket(), status, name,
conditions, listeners, data);
-
- // Note : arraylist implementation in simulation different from standard one
- for (CustomExpression exp : customExpressions){
- exp.setSimulation(simulation);
- if (exp.checkAll())
- simulation.addCustomExpression(exp);
- }
-
+
doc.addSimulation(simulation);
}
}
-
-class CustomExpressionsHandler extends AbstractElementHandler {
- private final DocumentLoadingContext context;
- private final SingleSimulationHandler simHandler;
- public CustomExpression currentExpression = new CustomExpression();
- private final ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
-
-
- public CustomExpressionsHandler(SingleSimulationHandler simHandler, DocumentLoadingContext context) {
- this.context = context;
- this.simHandler = simHandler;
- }
-
- @Override
- public ElementHandler openElement(String element,
- HashMap<String, String> attributes, WarningSet warnings)
- throws SAXException {
-
- if (element.equals("expression")){
- currentExpression = new CustomExpression();
- }
-
- return this;
- }
-
- @Override
- public void closeElement(String element, HashMap<String, String> attributes,
- String content, WarningSet warnings) {
-
- if (element.equals("expression"))
- customExpressions.add(currentExpression);
-
- if (element.equals("name"))
- currentExpression.setName(content);
-
- else if (element.equals("symbol"))
- currentExpression.setSymbol(content);
-
- else if (element.equals("unit"))
- currentExpression.setUnit(content);
-
- else if (element.equals("expressionstring"))
- currentExpression.setExpression(content);
-
- }
-
- @Override
- public void endHandler(String element, HashMap<String, String> attributes,
- String content, WarningSet warnings) {
- simHandler.setCustomExpressions(customExpressions);
- }
-}
-
+
class SimulationConditionsHandler extends AbstractElementHandler {
private final DocumentLoadingContext context;
private SimulationOptions conditions;
private FlightDataBranchHandler dataHandler;
private WarningSet warningSet = new WarningSet();
private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
-
+
private SingleSimulationHandler simHandler;
private FlightData data;
return null;
}
dataHandler = new FlightDataBranchHandler( attributes.get("name"),
- attributes.get("typekeys"),
- attributes.get("types"),
- simHandler, context);
+ attributes.get("types"),
+ simHandler, context);
return dataHandler;
}
private final DocumentLoadingContext context;
private final FlightDataType[] types;
private final FlightDataBranch branch;
-
+
private static final LogHelper log = Application.getLogger();
private final SingleSimulationHandler simHandler;
-
- public FlightDataBranchHandler(String name, String typeKeyList, String typeList, SingleSimulationHandler simHandler, DocumentLoadingContext context) {
+
+ public FlightDataBranchHandler(String name, String typeList, SingleSimulationHandler simHandler, DocumentLoadingContext context) {
this.simHandler = simHandler;
this.context = context;
- String[] typeNames = typeList.split(",");
- String[] typeKeys = null;
- if ( typeKeyList != null ) {
- typeKeys = typeKeyList.split(",");
- }
- types = new FlightDataType[typeNames.length];
- for (int i = 0; i < typeNames.length; i++) {
- String typeName = typeNames[i];
- String typeKey = (typeKeys != null ) ? typeKeys[i] : null ;
- FlightDataType matching = findFlightDataType(typeKey, typeName);
+ String[] split = typeList.split(",");
+ types = new FlightDataType[split.length];
+ for (int i = 0; i < split.length; i++) {
+ String typeName = split[i];
+ FlightDataType matching = findFlightDataType(typeName);
types[i] = matching;
//types[i] = FlightDataType.getType(typeName, matching.getSymbol(), matching.getUnitGroup());
}
// TODO: LOW: May throw an IllegalArgumentException
branch = new FlightDataBranch(name, types);
}
-
+
// Find the full flight data type given name only
// Note: this way of doing it requires that custom expressions always come before flight data in the file,
// not the nicest but this is always the case anyway.
- private FlightDataType findFlightDataType(String key, String name){
-
- // Look in built in types by key.
+ private FlightDataType findFlightDataType(String name){
+
+ // Kevins version with lookup by key. Not using right now
+ /*
if ( key != null ) {
for (FlightDataType t : FlightDataType.ALL_TYPES){
if (t.getKey().equals(key) ){
}
}
}
- // Look in built in types by name.
+ */
+
+ // Look in built in types
for (FlightDataType t : FlightDataType.ALL_TYPES){
if (t.getName().equals(name) ){
return t;
}
}
-
+
// Look in custom expressions
- for (CustomExpression exp : simHandler.getCustomExpressions()){
- if (exp.getName().equals(name) ){
- return exp.getType();
- }
- }
-
- // Look in custom expressions, meanwhile set priority based on order in file
- /*
- int totalExpressions = simHandler.getCustomExpressions().size();
- for (int i=0; i<totalExpressions; i++){
- CustomExpression exp = simHandler.getCustomExpressions().get(i);
+ for (CustomExpression exp : simHandler.getDocument().getCustomExpressions()){
if (exp.getName().equals(name) ){
- FlightDataType t = exp.getType();
- t.setPriority(-1*(totalExpressions-i));
return exp.getType();
}
}
- */
-
+
log.warn("Could not find the flight data type '"+name+"' used in the XML file. Substituted type with unknown symbol and units.");
return FlightDataType.getType(name, "Unknown", UnitGroup.UNITS_NONE);
}
////ComponentPresetSetter - sets a ComponentPreset value
class ComponentPresetSetter implements Setter {
- private final Reflection.Method setMethod;
+private final Reflection.Method setMethod;
- public ComponentPresetSetter(Reflection.Method set) {
- this.setMethod = set;
- }
+public ComponentPresetSetter(Reflection.Method set) {
+ this.setMethod = set;
+}
- @Override
- public void set(RocketComponent c, String name, HashMap<String, String> attributes,
- WarningSet warnings) {
- // FIXME - probably need more data in the warning messages - like what component preset...
- String manufacturerName = attributes.get("manufacturer");
- if ( manufacturerName == null ) {
- warnings.add(Warning.fromString("Invalid ComponentPreset, no manufacturer specified. Ignored"));
- return;
- }
+@Override
+public void set(RocketComponent c, String name, HashMap<String, String> attributes,
+ WarningSet warnings) {
+ // FIXME - probably need more data in the warning messages - like what component preset...
+ String manufacturerName = attributes.get("manufacturer");
+ if ( manufacturerName == null ) {
+ warnings.add(Warning.fromString("Invalid ComponentPreset, no manufacturer specified. Ignored"));
+ return;
+ }
- String productNo = attributes.get("partno");
- if ( productNo == null ) {
- warnings.add(Warning.fromString("Invalid ComponentPreset, no partno specified. Ignored"));
- return;
- }
+ String productNo = attributes.get("partno");
+ if ( productNo == null ) {
+ warnings.add(Warning.fromString("Invalid ComponentPreset, no partno specified. Ignored"));
+ return;
+ }
- String digest = attributes.get("digest");
- if ( digest == null ) {
- warnings.add(Warning.fromString("Invalid ComponentPreset, no digest specified."));
- }
+ String digest = attributes.get("digest");
+ if ( digest == null ) {
+ warnings.add(Warning.fromString("Invalid ComponentPreset, no digest specified."));
+ }
- String type = attributes.get("type");
- if ( type == null ) {
- warnings.add(Warning.fromString("Invalid ComponentPreset, no type specified."));
- }
+ String type = attributes.get("type");
+ if ( type == null ) {
+ warnings.add(Warning.fromString("Invalid ComponentPreset, no type specified."));
+ }
- List<ComponentPreset> presets = Application.getComponentPresetDao().find( manufacturerName, productNo );
+ List<ComponentPreset> presets = Application.getComponentPresetDao().find( manufacturerName, productNo );
- ComponentPreset matchingPreset = null;
+ ComponentPreset matchingPreset = null;
- for( ComponentPreset preset: presets ) {
- if ( digest != null && preset.getDigest().equals(digest) ) {
- // Found one with matching digest. Take it.
- matchingPreset = preset;
- break;
- }
- if ( type != null && preset.getType().name().equals(type) && matchingPreset != null) {
- // Found the first one with matching type.
- matchingPreset = preset;
- }
+ for( ComponentPreset preset: presets ) {
+ if ( digest != null && preset.getDigest().equals(digest) ) {
+ // Found one with matching digest. Take it.
+ matchingPreset = preset;
+ break;
}
-
- // Was any found?
- if ( matchingPreset == null ) {
- warnings.add(Warning.fromString("No matching ComponentPreset found " + manufacturerName + " " + productNo));
- return;
+ if ( type != null && preset.getType().name().equals(type) && matchingPreset != null) {
+ // Found the first one with matching type.
+ matchingPreset = preset;
}
+ }
- if ( digest != null && !matchingPreset.getDigest().equals(digest) ) {
- warnings.add(Warning.fromString("ComponentPreset has wrong digest"));
- }
+ // Was any found?
+ if ( matchingPreset == null ) {
+ warnings.add(Warning.fromString("No matching ComponentPreset found " + manufacturerName + " " + productNo));
+ return;
+ }
- setMethod.invoke(c, matchingPreset);
+ if ( digest != null && !matchingPreset.getDigest().equals(digest) ) {
+ warnings.add(Warning.fromString("ComponentPreset has wrong digest"));
}
+
+ setMethod.invoke(c, matchingPreset);
+}
}
////MaterialSetter - sets a Material value
class MaterialSetter implements Setter {
- private final Reflection.Method setMethod;
- private final Material.Type type;
+private final Reflection.Method setMethod;
+private final Material.Type type;
- public MaterialSetter(Reflection.Method set, Material.Type type) {
- this.setMethod = set;
- this.type = type;
- }
+public MaterialSetter(Reflection.Method set, Material.Type type) {
+ this.setMethod = set;
+ this.type = type;
+}
- @Override
- public void set(RocketComponent c, String name, HashMap<String, String> attributes,
- WarningSet warnings) {
+@Override
+public void set(RocketComponent c, String name, HashMap<String, String> attributes,
+ WarningSet warnings) {
- Material mat;
+ Material mat;
- // Check name != ""
- name = name.trim();
- if (name.equals("")) {
- warnings.add(Warning.fromString("Illegal material specification, ignoring."));
- return;
- }
+ // Check name != ""
+ name = name.trim();
+ if (name.equals("")) {
+ warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+ return;
+ }
- // Parse density
- double density;
- String str;
- str = attributes.remove("density");
- if (str == null) {
- warnings.add(Warning.fromString("Illegal material specification, ignoring."));
- return;
- }
- try {
- density = Double.parseDouble(str);
- } catch (NumberFormatException e) {
- warnings.add(Warning.fromString("Illegal material specification, ignoring."));
- return;
- }
+ // Parse density
+ double density;
+ String str;
+ str = attributes.remove("density");
+ if (str == null) {
+ warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+ return;
+ }
+ try {
+ density = Double.parseDouble(str);
+ } catch (NumberFormatException e) {
+ warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+ return;
+ }
- // Parse thickness
- // double thickness = 0;
- // str = attributes.remove("thickness");
- // try {
- // if (str != null)
- // thickness = Double.parseDouble(str);
- // } catch (NumberFormatException e){
- // warnings.add(Warning.fromString("Illegal material specification, ignoring."));
- // return;
- // }
-
- // Check type if specified
- str = attributes.remove("type");
- if (str != null && !type.name().toLowerCase(Locale.ENGLISH).equals(str)) {
- warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
- return;
- }
+ // Parse thickness
+ // double thickness = 0;
+ // str = attributes.remove("thickness");
+ // try {
+ // if (str != null)
+ // thickness = Double.parseDouble(str);
+ // } catch (NumberFormatException e){
+ // warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+ // return;
+ // }
- String key = attributes.remove("key");
+ // Check type if specified
+ str = attributes.remove("type");
+ if (str != null && !type.name().toLowerCase(Locale.ENGLISH).equals(str)) {
+ warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
+ return;
+ }
- mat = Databases.findMaterial(type, key, name, density);
+ String key = attributes.remove("key");
- setMethod.invoke(c, mat);
- }
+ mat = Databases.findMaterial(type, key, name, density);
+
+ setMethod.invoke(c, mat);
}
+}
+
import net.sf.openrocket.rocketcomponent.ReferenceType;
import net.sf.openrocket.rocketcomponent.Rocket;
-
public class RocketSaver extends RocketComponentSaver {
private static final RocketSaver instance = new RocketSaver();
return list;
}
-
-
@Override
protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
super.addParams(c, elements);
--- /dev/null
+package net.sf.openrocket.gui.customexpression;
+
+import java.awt.Window;
+
+import javax.swing.BorderFactory;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.util.GUIUtil;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.startup.Application;
+
+public class CustomExpressionDialog extends JDialog {
+ private static final Translator trans = Application.getTranslator();
+ private static final LogHelper log = Application.getLogger();
+
+ private final Window parentWindow;
+ private final OpenRocketDocument doc;
+
+ public CustomExpressionDialog(OpenRocketDocument doc, Window parent){
+ super(parent, trans.get("customExpressionPanel.lbl.CustomExpressions"));
+
+ this.doc = doc;
+ this.parentWindow = parent;
+
+ JPanel panel = new CustomExpressionPanel(doc, this);
+ this.add( panel );
+
+ GUIUtil.setDisposableDialogOptions(this, null);
+ }
+}
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import javax.swing.BorderFactory;
import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.filechooser.FileNameExtensionFilter;
import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.DatabaseMotorFinder;
+import net.sf.openrocket.file.GeneralRocketLoader;
+import net.sf.openrocket.file.MotorFinder;
+import net.sf.openrocket.file.RocketLoadException;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.components.DescriptionArea;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.startup.Application;
public class CustomExpressionPanel extends JPanel {
private static final Translator trans = Application.getTranslator();
private JPanel expressionSelectorPanel;
- private Simulation simulation;
+ private OpenRocketDocument doc;
- public CustomExpressionPanel(final Simulation simulation) {
+ public CustomExpressionPanel(final OpenRocketDocument doc, final JDialog parentDialog) {
super(new MigLayout("fill"));
- this.simulation = simulation;
+ this.doc = doc;
expressionSelectorPanel = new JPanel(new MigLayout("gapy rel"));
- JScrollPane scroll = new JScrollPane(expressionSelectorPanel);
- this.add(scroll, "spany 2, height 10px, wmin 400lp, grow 100, gapright para");
+ expressionSelectorPanel.setToolTipText(trans.get("customExpressionPanel.lbl.CalcNote"));
- DescriptionArea desc = new DescriptionArea(trans.get("customExpressionPanel.lbl.UpdateNote")+"\n\n"+trans.get("customExpressionPanel.lbl.CalcNote"), 8, -2f);
- desc.setViewportBorder(BorderFactory.createEmptyBorder());
- this.add(desc, "width 1px, growx 1, wrap unrel");
+ JScrollPane scroll = new JScrollPane();
+ Border bdr = BorderFactory.createTitledBorder(trans.get("customExpressionPanel.lbl.CustomExpressions"));
+
+ expressionSelectorPanel.setBorder(bdr);
+ expressionSelectorPanel.add(scroll);
+
+ //this.add(expressionSelectorPanel, "spany 1, height 10px, wmin 600lp, grow 100, gapright para");
+ this.add(expressionSelectorPanel, "hmin 200lp, wmin 700lp, grow 100, wrap");
+
+ //DescriptionArea desc = new DescriptionArea(trans.get("customExpressionPanel.lbl.UpdateNote")+"\n\n"+trans.get("customExpressionPanel.lbl.CalcNote"), 8, -2f);
+ //desc.setViewportBorder(BorderFactory.createEmptyBorder());
+ //this.add(desc, "width 1px, growx 1, wrap unrel, wrap");
//// New expression
JButton button = new JButton(trans.get("customExpressionPanel.but.NewExpression"));
+ button.setToolTipText(trans.get("customExpressionPanel.but.ttip.NewExpression"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Open window to configure expression
- log.debug("Opening window to configure new expression");
+ log.info("Opening window to configure new expression");
Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this);
- new ExpressionBuilderDialog(parent, simulation).setVisible(true);
+ new ExpressionBuilderDialog(parent, doc).setVisible(true);
updateExpressions();
}
});
+ this.add(button, "split 4, width :100:200");
+
+ //// Import
+ final JButton importButton = new JButton(trans.get("customExpressionPanel.but.Import"));
+ importButton.setToolTipText(trans.get("customExpressionPanel.but.ttip.Import"));
+ importButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+
+ //Create a file chooser
+ final JFileChooser fc = new JFileChooser();
+ if (doc.getFile() != null){
+ fc.setCurrentDirectory(doc.getFile().getParentFile());
+ }
+ fc.setFileFilter(new FileNameExtensionFilter("Openrocket file", "ork"));
+ fc.setAcceptAllFileFilterUsed(false);
- this.add(button, "left");
+ int returnVal = fc.showOpenDialog(CustomExpressionPanel.this);
+ if (returnVal == JFileChooser.APPROVE_OPTION){
+ File importFile = fc.getSelectedFile();
+ log.info("User selected a file to import expressions from "+fc.getSelectedFile().toString());
+
+ //TODO: This should probably be somewhere else and ideally we would use an alternative minimal rocket loader. Still, it doesn't seem particularly slow this way.
+
+ // Load expressions from selected document
+ GeneralRocketLoader loader = new GeneralRocketLoader();
+ try {
+ OpenRocketDocument importedDocument = loader.load(importFile, new DatabaseMotorFinder());
+ for (CustomExpression exp : importedDocument.getCustomExpressions()){
+ doc.addCustomExpression(exp);
+ }
+ } catch (RocketLoadException e1) {
+ log.user("Error opening document to import expressions from.");
+ }
+ updateExpressions();
+ }
+ }
+ });
+ this.add(importButton, "width :100:200");
+
+ //// Close button
+ final JButton closeButton = new JButton(trans.get("dlg.but.close"));
+ closeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ parentDialog.dispose();
+ }
+ });
+ this.add(new JPanel(), "growx");
+ this.add(closeButton, "width :100:200");
updateExpressions();
}
private void updateExpressions(){
expressionSelectorPanel.removeAll();
- int totalExpressions = simulation.getCustomExpressions().size();
+ int totalExpressions = doc.getCustomExpressions().size();
for (int i=0; i<totalExpressions; i++){
- SingleExpression se = new SingleExpression(simulation.getCustomExpressions().get(i), i != 0, i != totalExpressions-1);
+ SingleExpression se = new SingleExpression(doc.getCustomExpressions().get(i), i != 0, i != totalExpressions-1);
expressionSelectorPanel.add(se, "wrap");
}
- //TODO: High : Find out why repaint method not working properly here.
- //expressionSelectorPanel.repaint();
- expressionSelectorPanel.updateUI(); // Not the correct method to use but works
+ expressionSelectorPanel.revalidate();
+ expressionSelectorPanel.repaint();
}
private void deleteExpression(CustomExpression expression){
- simulation.getCustomExpressions().remove(expression);
+ doc.getCustomExpressions().remove(expression);
}
/**
* @param move integer - +1 to move down, -1 to move up
*/
private void moveExpression(CustomExpression expression, int move){
- ArrayList<CustomExpression> expressions = simulation.getCustomExpressions();
+ ArrayList<CustomExpression> expressions = doc.getCustomExpressions();
int i = expressions.indexOf(expression);
if (i+move == expressions.size() || i+move < 0)
return;
JLabel unitLabel = new JLabel( trans.get("customExpression.Units")+ " :");
UnitSelector unitSelector = new UnitSelector(expression.getType().getUnitGroup());
+ //JLabel unitSelector = new JLabel ( expression.getUnit() );
+ //unitSelector = setLabelStyle(unitSelector);
+ //unitSelector.setBackground(Color.WHITE);
JButton editButton = new JButton(Icons.EDIT);
editButton.setToolTipText(trans.get("customExpression.Units.but.ttip.Edit"));
@Override
public void actionPerformed(ActionEvent e){
Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this);
- new ExpressionBuilderDialog(parent, expression.getSimulation(), expression).setVisible(true);
+ new ExpressionBuilderDialog(parent, doc, expression).setVisible(true);
updateExpressions();
}
});
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.startup.Application;
/**
private CustomExpression previousExpressionCopy;
private final Window parentWindow;
- private final Simulation simulation;
+ private final OpenRocketDocument doc;
// Define these check indicators to show if fields are OK
private final JLabel nameCheck = new JLabel(RedIcon);
private final JButton okButton = new JButton(trans.get("dlg.but.ok"));
private final JTextField expressionField = new JTextField(20);
- public ExpressionBuilderDialog(Window parent, Simulation simulation){
- this(parent, simulation, new CustomExpression(simulation));
+ public ExpressionBuilderDialog(Window parent, OpenRocketDocument doc){
+ this(parent, doc, new CustomExpression(doc));
}
- public ExpressionBuilderDialog(Window parent, final Simulation simulation, final CustomExpression previousExpression){
+ public ExpressionBuilderDialog(Window parent, final OpenRocketDocument doc, final CustomExpression previousExpression){
super(parent, trans.get("ExpressionBuilderDialog.title"), JDialog.ModalityType.DOCUMENT_MODAL);
+ this.doc = doc;
this.parentWindow = parent;
- this.simulation = simulation;
this.previousExpressionCopy = (CustomExpression) previousExpression.clone();
this.expression = previousExpression;
public void actionPerformed(ActionEvent e) {
log.debug("Opening insert variable window");
Window parentWindow = SwingUtilities.getWindowAncestor(ExpressionBuilderDialog.this);
- new VariableSelector(parentWindow, ExpressionBuilderDialog.this, simulation).setVisible(true);
+ new VariableSelector(parentWindow, ExpressionBuilderDialog.this, doc).setVisible(true);
}
});
}
});
- //// Copy expression check box
- final JCheckBox copyCheckBox = new JCheckBox(trans.get("ExpressionBuilderDialog.CopyToOtherSimulations"));
- copyCheckBox.setHorizontalTextPosition(SwingConstants.LEFT);
- copyCheckBox.setToolTipText(trans.get("ExpressionBuilderDialog.CopyToOtherSimulations.ttip"));
-
//// OK Button
okButton.setEnabled(false);
okButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// add to this simulation
- expression.addToSimulation();
- if (copyCheckBox.isSelected()){
- expression.copyToOtherSimulations();
- }
+ expression.addToDocument();
// close window
ExpressionBuilderDialog.this.dispose();
mainPanel.add(expressionCheck, "wrap, center");
mainPanel.add(insertOperatorButton, "span 2, right, split 2");
mainPanel.add(insertVariableButton, "right, wrap");
- mainPanel.add(copyCheckBox, "span 2, right, wrap");
mainPanel.add(cancelButton, "span 2, right, width :50:100");
mainPanel.add(okButton, "right, width :50:100, wrap");
package net.sf.openrocket.gui.customexpression;
+import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionAdapter;
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
import javax.swing.JButton;
+import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
+import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.TextUtil;
public class OperatorSelector extends JDialog {
private final Window parentWindow;
+ private final JTable table;
+ private final OperatorTableModel tableModel;
+ private final ExpressionBuilderDialog parentBuilder;
+
public OperatorSelector(Window parent, final ExpressionBuilderDialog parentBuilder){
super(parent, trans.get("CustomOperatorSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL);
this.parentWindow = parent;
+ this.parentBuilder = parentBuilder;
final JButton insertButton = new JButton(trans.get("ExpressionBuilderDialog.InsertOperator"));
JPanel mainPanel = new JPanel(new MigLayout());
//// Table of variables and model
- final OperatorTableModel tableModel = new OperatorTableModel();
- final JTable table = new JTable(tableModel);
+ tableModel = new OperatorTableModel();
+ table = new JTable(tableModel);
table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
int width = table.getColumnModel().getTotalColumnWidth();
- table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.2 * width));
- table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.8 * width));
+ table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.1 * width));
+ table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.9 * width));
+ table.setAutoCreateRowSorter(true);
+
+ table.addMouseMotionListener(new MouseMotionAdapter(){
+ @Override
+ public void mouseMoved(MouseEvent e){
+ Point p = e.getPoint();
+ int row = table.rowAtPoint(p);
+ int col = table.columnAtPoint(p);
+ if (col == 1){
+ String description = String.valueOf(table.getValueAt(row, 1));
+ description = TextUtil.wrap(description, 60);
+ table.setToolTipText(description);
+ } else {
+ table.setToolTipText(null);
+ }
+ }
+ });
+
+ table.addMouseListener(new MouseListener(){
+ @Override
+ public void mouseClicked(MouseEvent e){
+ if (e.getClickCount() == 2){
+ log.debug("Selected operator by double clicking.");
+ selectOperator();
+ }
+ }
+ @Override
+ public void mouseEntered(MouseEvent e) {}
+ @Override
+ public void mouseExited(MouseEvent e) {}
+ @Override
+ public void mousePressed(MouseEvent e) {}
+ @Override
+ public void mouseReleased(MouseEvent e) {}
+ } );
+
+ InputMap inputMap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ ActionMap actionMap = table.getActionMap();
+ KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
+ inputMap.put(enter, "select");
+ actionMap.put("select", new AbstractAction(){
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ log.debug("Selected operator by enter key");
+ selectOperator();
+ }
+ });
JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);
}
});
- mainPanel.add(scrollPane, "wrap");
+ mainPanel.add(scrollPane, "wrap, push, grow");
//// Cancel button
final JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
insertButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- int row = table.getSelectedRow();
- String str = tableModel.getOperatorAt(row);
- parentBuilder.pasteIntoExpression(str);
- OperatorSelector.this.dispose();
+ selectOperator();
}
});
insertButton.setEnabled(false); // disabled by default, only enable when a variable selected
this.pack();
this.setLocationByPlatform(true);
}
+
+ private void selectOperator(){
+ int row = table.getSelectedRow();
+ String str = tableModel.getOperatorAt(row);
+ parentBuilder.pasteIntoExpression(str);
+ OperatorSelector.this.dispose();
+ }
}
import javax.swing.table.AbstractTableModel;
import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.Functions;
import net.sf.openrocket.startup.Application;
public class OperatorTableModel extends AbstractTableModel {
private static final String[] columnNames = {trans.get("customExpression.Operator"), trans.get("customExpression.Description")};
- private final Object[] operators = CustomExpression.AVAILABLE_OPERATORS.keySet().toArray();
- private final Object[] descriptions = CustomExpression.AVAILABLE_OPERATORS.values().toArray();
+ private final Object[] operators = Functions.AVAILABLE_OPERATORS.keySet().toArray();
+ private final Object[] descriptions = Functions.AVAILABLE_OPERATORS.values().toArray();
public OperatorTableModel(){
@Override
public int getRowCount() {
- return CustomExpression.AVAILABLE_OPERATORS.size();
+ return Functions.AVAILABLE_OPERATORS.size();
}
@Override
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
import javax.swing.JButton;
+import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
+import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
+import javax.swing.table.JTableHeader;
import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.startup.Application;
/**
*/
public class VariableSelector extends JDialog {
-
+
private static final Translator trans = Application.getTranslator();
private static final LogHelper log = Application.getLogger();
-
- private final Window parentWindow;
- private final Simulation simulation;
- public VariableSelector(Window parent, final ExpressionBuilderDialog parentBuilder, final Simulation simulation){
-
+ private final JTable table;
+ private final VariableTableModel tableModel;
+ private final ExpressionBuilderDialog parentBuilder;
+
+ public VariableSelector(Window parent, final ExpressionBuilderDialog parentBuilder, final OpenRocketDocument doc){
+
super(parent, trans.get("CustomVariableSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL);
-
- this.parentWindow = parent;
- this.simulation = simulation;
-
+
+ this.parentBuilder = parentBuilder;
final JButton insertButton = new JButton(trans.get("ExpressionBuilderDialog.InsertVariable"));
-
+
JPanel mainPanel = new JPanel(new MigLayout());
-
+
//// Table of variables and model
- final VariableTableModel tableModel = new VariableTableModel(simulation);
- final JTable table = new JTable(tableModel);
+ tableModel = new VariableTableModel(doc);
+ table = new JTable(tableModel);
+ table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
int width = table.getColumnModel().getTotalColumnWidth();
table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.7 * width));
table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.15 * width));
table.getColumnModel().getColumn(2).setPreferredWidth( (int) (.15 * width));
+ table.setAutoCreateRowSorter(true);
+
+ table.addMouseListener(new MouseListener(){
+ @Override
+ public void mouseClicked(MouseEvent e){
+ if (e.getClickCount() == 2){
+ log.debug("Selected variable by double clicking.");
+ selectVariable();
+ }
+ }
+ @Override
+ public void mouseEntered(MouseEvent e) {}
+ @Override
+ public void mouseExited(MouseEvent e) {}
+ @Override
+ public void mousePressed(MouseEvent e) {}
+ @Override
+ public void mouseReleased(MouseEvent e) {}
+ } );
+
+ InputMap inputMap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ ActionMap actionMap = table.getActionMap();
+ KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
+ inputMap.put(enter, "select");
+ actionMap.put("select", new AbstractAction(){
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ log.debug("Selected variable by enter key");
+ selectVariable();
+ }
+ });
+
JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
- @Override
- public void valueChanged(ListSelectionEvent e){
- if (table.getSelectedRowCount() == 1){
- insertButton.setEnabled(true);
- }
- else {
- insertButton.setEnabled(false);
- }
+ @Override
+ public void valueChanged(ListSelectionEvent e){
+ if (table.getSelectedRowCount() == 1){
+ insertButton.setEnabled(true);
}
- });
-
- mainPanel.add(scrollPane, "wrap");
-
+ else {
+ insertButton.setEnabled(false);
+ }
+ }
+ });
+
+ mainPanel.add(scrollPane, "wrap, push, grow");
+
//// Cancel button
final JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
cancelButton.addActionListener(new ActionListener() {
}
});
mainPanel.add(cancelButton, "right, width :100:200, split 2");
-
+
//// Insert button
insertButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- int row = table.getSelectedRow();
- String str = tableModel.getSymbolAt(row);
- parentBuilder.pasteIntoExpression(str);
- VariableSelector.this.dispose();
+ selectVariable();
}
});
insertButton.setEnabled(false); // disabled by default, only enable when a variable selected
this.pack();
this.setLocationByPlatform(true);
}
+
+ private void selectVariable(){
+ int row = table.getSelectedRow();
+ String str = tableModel.getSymbolAt(row);
+ parentBuilder.pasteIntoExpression(str);
+ VariableSelector.this.dispose();
+ }
+
}
*/
package net.sf.openrocket.gui.customexpression;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
+import java.util.Vector;
+import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
-import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.startup.Application;
/*
* Table model will be constructed with all the built in variables and any custom variables defined
*/
- public VariableTableModel(Simulation sim){
+ public VariableTableModel(OpenRocketDocument doc){
Collections.addAll(types, FlightDataType.ALL_TYPES);
- for (CustomExpression expression : sim.getCustomExpressions()){
+ for (CustomExpression expression : doc.getCustomExpressions()){
types.add(expression.getType());
}
-
}
@Override
else if (col == 1)
return types.get(row).getSymbol();
else if (col == 2)
- return types.get(row).getUnitGroup().getDefaultUnit().toString();
+ return types.get(row).getUnitGroup().getSIUnit().toString();
return null;
}
import net.sf.openrocket.file.rocksim.export.RocksimSaver;
import net.sf.openrocket.gui.StorageOptionChooser;
import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
+import net.sf.openrocket.gui.customexpression.CustomExpressionDialog;
import net.sf.openrocket.gui.dialogs.AboutDialog;
import net.sf.openrocket.gui.dialogs.BugReportDialog;
import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
});
menu.add(item);
-
+ //// Optimize
item = new JMenuItem(trans.get("main.menu.analyze.optimization"), KeyEvent.VK_O);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.optimization.desc"));
item.addActionListener(new ActionListener() {
});
menu.add(item);
-
+ //// Custom expressions
+ item = new JMenuItem(trans.get("main.menu.analyze.customExpressions"), KeyEvent.VK_E);
+ item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.customExpressions.desc"));
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.debug("Custom expressions selected");
+ new CustomExpressionDialog(document, BasicFrame.this).setVisible(true);
+ }
+ });
+ menu.add(item);
//// Debug
// (shown if openrocket.debug.menu is defined)
public static final int DEFAULT = -1;
public static final int EDIT = 1;
- public static final int PLOT = 3;
+ public static final int PLOT = 2;
private final Window parentWindow;
tabbedPane.addTab(trans.get("simedtdlg.tab.Launchcond"), flightConditionsTab());
//// Simulation options
tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), simulationOptionsTab());
- //// Custom expressions tab
- tabbedPane.addTab(trans.get("simedtdlg.tab.CustomExpressions"), customExpressionsTab());
//// Plot data
tabbedPane.addTab(trans.get("simedtdlg.tab.Plotdata"), plotTab());
//// Export data
if (tab == EDIT) {
tabbedPane.setSelectedIndex(0);
} else if (tab == PLOT) {
- tabbedPane.setSelectedIndex(3);
+ tabbedPane.setSelectedIndex(2);
} else {
FlightData data = s.getSimulatedData();
if (data == null || data.getBranchCount() == 0)
return new SimulationExportPanel(simulation);
}
-
- private JPanel customExpressionsTab() {
- return new CustomExpressionPanel(simulation);
- }
-
/**
* Return a panel stating that there is no data available, and that the user
* List of component change listeners.
*/
private List<EventListener> listenerList = new ArrayList<EventListener>();
-
+
/**
* When freezeList != null, events are not dispatched but stored in the list.
* When the structure is thawed, a single combined event will be fired.
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
-
/**
* Return the number of stages in this rocket.
*
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.Stage;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.exception.MotorIgnitionException;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationLaunchException;
// Calculate values for custom expressions
FlightDataBranch data = status.getFlightData();
- ArrayList<CustomExpression> allExpressions = status.getSimulationConditions().getSimulation().getCustomExpressions();
+ ArrayList<CustomExpression> allExpressions = status.getSimulationConditions().getSimulation().getDocument().getCustomExpressions();
for (CustomExpression expression : allExpressions ) {
- data.setValue(expression.getType(), expression.evaluate(status));
+ data.setValue(expression.getType(), expression.evaluateDouble(status));
}
// Check for NaN values in the simulation status
+++ /dev/null
-package net.sf.openrocket.simulation;
-
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.unit.FixedUnitGroup;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.ArrayList;
-import de.congrace.exp4j.Calculable;
-import de.congrace.exp4j.ExpressionBuilder;
-
-
-/**
- * Represents a single custom expression
- * @author Richard Graham
- *
- */
-public class CustomExpression implements Cloneable{
-
- private static final LogHelper log = Application.getLogger();
- private static final Translator trans = Application.getTranslator();
-
- private String name, symbol, unit, expression;
- private ExpressionBuilder builder;
- private Simulation sim = null;
-
- // A map of available operator strings (keys) and description of function (value)
- public static final SortedMap<String, String> AVAILABLE_OPERATORS = new TreeMap<String, String>() {{
- put("+" , trans.get("Operator.plus"));
- put("-" , trans.get("Operator.minus"));
- put("*" , trans.get("Operator.star"));
- put("/" , trans.get("Operator.div"));
- put("%" , trans.get("Operator.mod"));
- put("^" , trans.get("Operator.pow"));
- put("abs()" , trans.get("Operator.abs"));
- put("ceil()" , trans.get("Operator.ceil"));
- put("floor()" , trans.get("Operator.floor"));
- put("sqrt()" , trans.get("Operator.sqrt"));
- put("cbrt()" , trans.get("Operator.cbrt"));
- put("exp()" , trans.get("Operator.exp"));
- put("log()" , trans.get("Operator.ln"));
- put("sin()" , trans.get("Operator.sin"));
- put("cos()" , trans.get("Operator.cos"));
- put("tan()" , trans.get("Operator.tan"));
- put("asin()" , trans.get("Operator.asin"));
- put("acos()" , trans.get("Operator.acos"));
- put("atan()" , trans.get("Operator.atan"));
- put("sinh()" , trans.get("Operator.hsin"));
- put("cosh()" , trans.get("Operator.hcos"));
- put("tanh()" , trans.get("Operator.htan"));
- }};
-
- public CustomExpression(){
- setName("");
- setSymbol("");
- setUnit("");
- setExpression("");
- }
-
- public CustomExpression(Simulation sim){
- this();
- setSimulation(sim);
- }
-
- public CustomExpression(Simulation sim, String name, String symbol, String unit, String expression) {
-
- setName(name);
- setSymbol(symbol);
- setUnit(unit);
- setExpression(expression);
- setSimulation(sim);
- }
-
- /*
- * Use this to update the simulation this is associated with
- */
- public void setSimulation(Simulation sim){
- this.sim = sim;
- }
-
- public Simulation getSimulation() {
- return this.sim;
- }
-
- /*
- * Returns the flight data branch 0 for this simulation, or an empty branch
- * if no simulated data exists
- */
- private FlightDataBranch getBranch() {
- if ( sim == null || sim.getSimulatedData() == null || sim.getSimulatedData().getBranchCount() == 0){
- return new FlightDataBranch();
- }
- else {
- return sim.getSimulatedData().getBranch(0);
- }
- }
-
-
- public void setName(String name){
- this.name = name;
- }
-
- public void setUnit(String unit){
- this.unit = unit;
- }
-
- public void setSymbol(String symbol){
- this.symbol = symbol;
- }
-
- public void setExpression(String expression){
- this.expression = expression;
- builder = new ExpressionBuilder(expression);
- }
-
- // get a list of all the names of all the available variables
- private ArrayList<String> getAllNames(){
- ArrayList<String> names = new ArrayList<String>();
- for (FlightDataType type : FlightDataType.ALL_TYPES)
- names.add(type.getName());
- for (CustomExpression exp : sim.getCustomExpressions() ){
- if (exp != this)
- names.add(exp.getName());
- }
- return names;
- }
-
- // get a list of all the symbols of the available variables ignoring this one
- private ArrayList<String> getAllSymbols(){
- ArrayList<String> symbols = new ArrayList<String>();
- for (FlightDataType type : FlightDataType.ALL_TYPES)
- symbols.add(type.getSymbol());
- for (CustomExpression exp : sim.getCustomExpressions() ){
- if (exp != this)
- symbols.add(exp.getSymbol());
- }
- return symbols;
- }
-
- public boolean checkSymbol(){
- if (symbol.trim().isEmpty())
- return false;
-
- // No bad characters
- for (char c : "0123456789.,()[]{}<> ".toCharArray())
- if (symbol.indexOf(c) != -1 )
- return false;
-
- // No operators (ignoring brackets)
- for (String s : CustomExpression.AVAILABLE_OPERATORS.keySet()){
- if (symbol.contains(s.replaceAll("\\(|\\)", "")))
- return false;
- }
-
- // No already defined symbols
- ArrayList<String> symbols = getAllSymbols().clone();
- if (symbols.contains(symbol.trim())){
- int index = symbols.indexOf(symbol.trim());
- log.user("Symbol "+symbol+" already exists, found "+symbols.get(index));
- return false;
- }
-
- return true;
- }
-
- public boolean checkName(){
- if (name.trim().isEmpty())
- return false;
-
- // No characters that could mess things up saving etc
- for (char c : ",()[]{}<>".toCharArray())
- if (name.indexOf(c) != -1 )
- return false;
-
- ArrayList<String> names = getAllNames().clone();
- if (names.contains(name.trim())){
- int index = names.indexOf(name.trim());
- log.user("Name "+name+" already exists, found "+names.get(index));
- return false;
- }
-
- return true;
- }
-
- // Currently no restrictions on unit
- public boolean checkUnit(){
- return true;
- }
-
- public boolean checkAll(){
- return checkUnit() && checkSymbol() && checkName();
- }
-
- public String getName(){
- return name;
- }
-
- public String getSymbol(){
- return symbol;
- }
-
- public String getUnit(){
- return unit;
- }
-
- public String getExpressionString(){
- return expression;
- }
-
-
- /*
- * Check if the current expression is valid
- */
- public boolean checkExpression(){
-
- if (expression.trim().isEmpty()){
- return false;
- }
-
- // Define the available variables as 0
- for (FlightDataType type : getBranch().getTypes()){
- builder.withVariable(type.getSymbol(), 0.0);
- }
-
- for (String symb : getAllSymbols()){
- builder.withVariable(symb, 0.0);
- }
-
- // Try to build
- try {
- builder.build();
- } catch (Exception e) {
- log.user("Custom expression invalid : " + e.toString());
- return false;
- }
-
- // Otherwise, all OK
- return true;
- }
-
- /*
- * Evaluate the expression using the last variable values from the simulation status.
- * Returns NaN on any error.
- */
- public Double evaluate(SimulationStatus status){
-
- for (FlightDataType type : status.getFlightData().getTypes()){
- builder.withVariable(type.getSymbol(), status.getFlightData().getLast(type) );
- }
-
- Calculable calc;
- try {
- calc = builder.build();
- return new Double(calc.calculate());
- } catch (Exception e) {
- log.user("Could not calculate custom expression "+name);
- return Double.NaN;
- }
- }
-
- /*
- * Returns the new flight data type corresponding to this calculated data
- */
- public FlightDataType getType(){
-
- UnitGroup ug = new FixedUnitGroup(unit);
- FlightDataType type = FlightDataType.getType(name, symbol, ug);
-
- // If in a simulation, figure out priority from order in array so that customs expressions are always at the top
- //if (sim != null && sim.getCustomExpressions().contains(this)){
- // int totalExpressions = sim.getCustomExpressions().size();
- // int p = -1*(totalExpressions-sim.getCustomExpressions().indexOf(this));
- // type.setPriority(p);
- //}
-
- return type;
- }
-
- /*
- * Add this expression to the simulation if valid and not already added
- */
- public void addToSimulation(){
- // Abort if exact expression already in
- if ( !sim.getCustomExpressions().contains(this) && this.checkAll() )
- sim.addCustomExpression( this );
- }
-
- /*
- * Removes this expression from the simulation, replacing it with a given new expression
- */
- public void overwrite(CustomExpression newExpression){
- if (!sim.getCustomExpressions().contains(this))
- return;
- else {
- int index = sim.getCustomExpressions().indexOf(this);
- sim.getCustomExpressions().set(index, newExpression);
- }
- }
-
- /*
- * Add a copy to other simulations in this document if possible
- * Will not overwrite existing expressions
- */
- public void copyToOtherSimulations(){
- for (Simulation s : this.getSimulation().getDocument().getSimulations()){
- CustomExpression newExpression = (CustomExpression) this.clone();
- newExpression.setSimulation(s);
- newExpression.addToSimulation();
- }
- }
-
- @Override
- public String toString(){
- return "Custom expression : "+this.name.toString()+ " " + this.expression.toString();
- }
-
- @Override
- /*
- * Clone method makes a deep copy of everything except the simulation.
- * If you want to apply this to another simulation, set simulation manually after cloning.
- * @see java.lang.Object#clone()
- */
- public Object clone() {
- try {
- return super.clone();
- }
- catch( CloneNotSupportedException e )
- {
- return new CustomExpression( sim ,
- new String(this.getName()),
- new String(this.getSymbol()),
- new String(this.getUnit()),
- new String(this.getExpressionString()));
- }
- }
-
-}
import java.util.Map;
import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
*/
public class FlightDataType implements Comparable<FlightDataType> {
private static final Translator trans = Application.getTranslator();
+ private static final LogHelper log = Application.getLogger();
/** Priority of custom-created variables */
private static final int DEFAULT_PRIORITY = 999;
/** List of existing types. MUST BE DEFINED BEFORE ANY TYPES!! */
+ /** NOTE: The String key here is now the symbol */
private static final Map<String, FlightDataType> EXISTING_TYPES = new HashMap<String, FlightDataType>();
-
//// Time
- public static final FlightDataType TYPE_TIME = newType("TYPE_TIME", "t", UnitGroup.UNITS_FLIGHT_TIME, 1);
+ public static final FlightDataType TYPE_TIME = newType(trans.get("FlightDataType.TYPE_TIME"), "t", UnitGroup.UNITS_FLIGHT_TIME, 1);
//// Vertical position and motion
//// Altitude
- public static final FlightDataType TYPE_ALTITUDE = newType("TYPE_ALTITUDE", "h", UnitGroup.UNITS_DISTANCE, 10);
+ public static final FlightDataType TYPE_ALTITUDE = newType(trans.get("FlightDataType.TYPE_ALTITUDE"), "h", UnitGroup.UNITS_DISTANCE, 10);
//// Vertical velocity
- public static final FlightDataType TYPE_VELOCITY_Z = newType("TYPE_VELOCITY_Z", "Vz", UnitGroup.UNITS_VELOCITY, 11);
+ public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), "Vz", UnitGroup.UNITS_VELOCITY, 11);
//// Vertical acceleration
- public static final FlightDataType TYPE_ACCELERATION_Z = newType("TYPE_ACCELERATION_Z", "Az", UnitGroup.UNITS_ACCELERATION, 12);
+ public static final FlightDataType TYPE_ACCELERATION_Z = newType(trans.get("FlightDataType.TYPE_ACCELERATION_Z"), "Az", UnitGroup.UNITS_ACCELERATION, 12);
//// Total motion
//// Total velocity
- public static final FlightDataType TYPE_VELOCITY_TOTAL = newType("TYPE_VELOCITY_TOTAL", "Vt", UnitGroup.UNITS_VELOCITY, 20);
+ public static final FlightDataType TYPE_VELOCITY_TOTAL = newType(trans.get("FlightDataType.TYPE_VELOCITY_TOTAL"), "Vt", UnitGroup.UNITS_VELOCITY, 20);
//// Total acceleration
- public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType("TYPE_ACCELERATION_TOTAL", "At", UnitGroup.UNITS_ACCELERATION, 21);
+ public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType(trans.get("FlightDataType.TYPE_ACCELERATION_TOTAL"), "At", UnitGroup.UNITS_ACCELERATION, 21);
//// Lateral position and motion
//// Position upwind
- public static final FlightDataType TYPE_POSITION_X = newType("TYPE_POSITION_X", "Px", UnitGroup.UNITS_DISTANCE, 30);
+ public static final FlightDataType TYPE_POSITION_X = newType(trans.get("FlightDataType.TYPE_POSITION_X"), "Px", UnitGroup.UNITS_DISTANCE, 30);
//// Position parallel to wind
- public static final FlightDataType TYPE_POSITION_Y = newType("TYPE_POSITION_Y", "Py", UnitGroup.UNITS_DISTANCE, 31);
+ public static final FlightDataType TYPE_POSITION_Y = newType(trans.get("FlightDataType.TYPE_POSITION_Y"), "Py", UnitGroup.UNITS_DISTANCE, 31);
//// Lateral distance
- public static final FlightDataType TYPE_POSITION_XY = newType("TYPE_POSITION_XY", "Pl", UnitGroup.UNITS_DISTANCE, 32);
+ public static final FlightDataType TYPE_POSITION_XY = newType(trans.get("FlightDataType.TYPE_POSITION_XY"), "Pl", UnitGroup.UNITS_DISTANCE, 32);
//// Lateral direction
- public static final FlightDataType TYPE_POSITION_DIRECTION = newType("TYPE_POSITION_DIRECTION", "\u03b8l", UnitGroup.UNITS_ANGLE, 33);
+ public static final FlightDataType TYPE_POSITION_DIRECTION = newType(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), "\u03b8l", UnitGroup.UNITS_ANGLE, 33);
//// Lateral velocity
- public static final FlightDataType TYPE_VELOCITY_XY = newType("TYPE_VELOCITY_XY", "Vl", UnitGroup.UNITS_VELOCITY, 34);
+ public static final FlightDataType TYPE_VELOCITY_XY = newType(trans.get("FlightDataType.TYPE_VELOCITY_XY"), "Vl", UnitGroup.UNITS_VELOCITY, 34);
//// Lateral acceleration
- public static final FlightDataType TYPE_ACCELERATION_XY = newType("TYPE_ACCELERATION_XY", "Al", UnitGroup.UNITS_ACCELERATION, 35);
+ public static final FlightDataType TYPE_ACCELERATION_XY = newType(trans.get("FlightDataType.TYPE_ACCELERATION_XY"), "Al", UnitGroup.UNITS_ACCELERATION, 35);
//// Latitude
- public static final FlightDataType TYPE_LATITUDE = newType("TYPE_LATITUDE", "\u03c6", UnitGroup.UNITS_ANGLE, 36);
+ public static final FlightDataType TYPE_LATITUDE = newType(trans.get("FlightDataType.TYPE_LATITUDE"), "\u03c6", UnitGroup.UNITS_ANGLE, 36);
//// Longitude
- public static final FlightDataType TYPE_LONGITUDE = newType("TYPE_LONGITUDE", "\u03bb", UnitGroup.UNITS_ANGLE, 37);
+ public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "\u03bb", UnitGroup.UNITS_ANGLE, 37);
//// Angular motion
//// Angle of attack
- public static final FlightDataType TYPE_AOA = newType("TYPE_AOA", "\u03b1", UnitGroup.UNITS_ANGLE, 40);
+ public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), "\u03b1", UnitGroup.UNITS_ANGLE, 40);
//// Roll rate
- public static final FlightDataType TYPE_ROLL_RATE = newType("TYPE_ROLL_RATE", "d\u03a6", UnitGroup.UNITS_ROLL, 41);
+ public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), "d\u03a6", UnitGroup.UNITS_ROLL, 41);
//// Pitch rate
- public static final FlightDataType TYPE_PITCH_RATE = newType("TYPE_PITCH_RATE", "d\u03b8", UnitGroup.UNITS_ROLL, 42);
+ public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), "d\u03b8", UnitGroup.UNITS_ROLL, 42);
//// Yaw rate
- public static final FlightDataType TYPE_YAW_RATE = newType("TYPE_YAW_RATE", "d\u03a8", UnitGroup.UNITS_ROLL, 43);
+ public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), "d\u03a8", UnitGroup.UNITS_ROLL, 43);
//// Stability information
//// Mass
- public static final FlightDataType TYPE_MASS = newType("TYPE_MASS", "m", UnitGroup.UNITS_MASS, 50);
+ public static final FlightDataType TYPE_MASS = newType(trans.get("FlightDataType.TYPE_MASS"), "m", UnitGroup.UNITS_MASS, 50);
//// Longitudinal moment of inertia
- public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType("TYPE_LONGITUDINAL_INERTIA", "Il", UnitGroup.UNITS_INERTIA, 51);
+ public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType(trans.get("FlightDataType.TYPE_LONGITUDINAL_INERTIA"), "Il", UnitGroup.UNITS_INERTIA, 51);
//// Rotational moment of inertia
- public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType("TYPE_ROTATIONAL_INERTIA", "Ir", UnitGroup.UNITS_INERTIA, 52);
+ public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType(trans.get("FlightDataType.TYPE_ROTATIONAL_INERTIA"), "Ir", UnitGroup.UNITS_INERTIA, 52);
//// CP location
- public static final FlightDataType TYPE_CP_LOCATION = newType("TYPE_CP_LOCATION", "Cp", UnitGroup.UNITS_LENGTH, 53);
+ public static final FlightDataType TYPE_CP_LOCATION = newType(trans.get("FlightDataType.TYPE_CP_LOCATION"), "Cp", UnitGroup.UNITS_LENGTH, 53);
//// CG location
- public static final FlightDataType TYPE_CG_LOCATION = newType("TYPE_CG_LOCATION", "Cg", UnitGroup.UNITS_LENGTH, 54);
+ public static final FlightDataType TYPE_CG_LOCATION = newType(trans.get("FlightDataType.TYPE_CG_LOCATION"), "Cg", UnitGroup.UNITS_LENGTH, 54);
//// Stability margin calibers
- public static final FlightDataType TYPE_STABILITY = newType("TYPE_STABILITY", "S", UnitGroup.UNITS_COEFFICIENT, 55);
+ public static final FlightDataType TYPE_STABILITY = newType(trans.get("FlightDataType.TYPE_STABILITY"), "S", UnitGroup.UNITS_COEFFICIENT, 55);
//// Characteristic numbers
//// Mach number
- public static final FlightDataType TYPE_MACH_NUMBER = newType("TYPE_MACH_NUMBER", "M", UnitGroup.UNITS_COEFFICIENT, 60);
+ public static final FlightDataType TYPE_MACH_NUMBER = newType(trans.get("FlightDataType.TYPE_MACH_NUMBER"), "M", UnitGroup.UNITS_COEFFICIENT, 60);
//// Reynolds number
- public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType("TYPE_REYNOLDS_NUMBER", "R", UnitGroup.UNITS_COEFFICIENT, 61);
+ public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType(trans.get("FlightDataType.TYPE_REYNOLDS_NUMBER"), "R", UnitGroup.UNITS_COEFFICIENT, 61);
//// Thrust and drag
//// Thrust
- public static final FlightDataType TYPE_THRUST_FORCE = newType("TYPE_THRUST_FORCE", "Ft", UnitGroup.UNITS_FORCE, 70);
+ public static final FlightDataType TYPE_THRUST_FORCE = newType(trans.get("FlightDataType.TYPE_THRUST_FORCE"), "Ft", UnitGroup.UNITS_FORCE, 70);
//// Drag force
- public static final FlightDataType TYPE_DRAG_FORCE = newType("TYPE_DRAG_FORCE", "Fd", UnitGroup.UNITS_FORCE, 71);
+ public static final FlightDataType TYPE_DRAG_FORCE = newType(trans.get("FlightDataType.TYPE_DRAG_FORCE"), "Fd", UnitGroup.UNITS_FORCE, 71);
//// Drag coefficient
- public static final FlightDataType TYPE_DRAG_COEFF = newType("TYPE_DRAG_COEFF", "Cd", UnitGroup.UNITS_COEFFICIENT, 72);
+ public static final FlightDataType TYPE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_DRAG_COEFF"), "Cd", UnitGroup.UNITS_COEFFICIENT, 72);
//// Axial drag coefficient
- public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType("TYPE_AXIAL_DRAG_COEFF", "Cda", UnitGroup.UNITS_COEFFICIENT, 73);
+ public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_AXIAL_DRAG_COEFF"), "Cda", UnitGroup.UNITS_COEFFICIENT, 73);
//// Component drag coefficients
//// Friction drag coefficient
- public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType("TYPE_FRICTION_DRAG_COEFF", "Cdf", UnitGroup.UNITS_COEFFICIENT, 80);
+ public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_FRICTION_DRAG_COEFF"), "Cdf", UnitGroup.UNITS_COEFFICIENT, 80);
//// Pressure drag coefficient
- public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType("TYPE_PRESSURE_DRAG_COEFF", "Cdp", UnitGroup.UNITS_COEFFICIENT, 81);
+ public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_PRESSURE_DRAG_COEFF"), "Cdp", UnitGroup.UNITS_COEFFICIENT, 81);
//// Base drag coefficient
- public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType("TYPE_BASE_DRAG_COEFF", "Cdb", UnitGroup.UNITS_COEFFICIENT, 82);
+ public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_BASE_DRAG_COEFF"), "Cdb", UnitGroup.UNITS_COEFFICIENT, 82);
//// Other coefficients
//// Normal force coefficient
- public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType("TYPE_NORMAL_FORCE_COEFF", "Cn", UnitGroup.UNITS_COEFFICIENT, 90);
+ public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_NORMAL_FORCE_COEFF"), "Cn", UnitGroup.UNITS_COEFFICIENT, 90);
//// Pitch moment coefficient
- public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType("TYPE_PITCH_MOMENT_COEFF", "C\u03b8", UnitGroup.UNITS_COEFFICIENT, 91);
+ public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), "C\u03b8", UnitGroup.UNITS_COEFFICIENT, 91);
//// Yaw moment coefficient
- public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType("TYPE_YAW_MOMENT_COEFF", "C\u03c4\u03a8", UnitGroup.UNITS_COEFFICIENT, 92);
+ public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), "C\u03c4\u03a8", UnitGroup.UNITS_COEFFICIENT, 92);
//// Side force coefficient
- public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType("TYPE_SIDE_FORCE_COEFF", "C\u03c4s", UnitGroup.UNITS_COEFFICIENT, 93);
+ public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), "C\u03c4s", UnitGroup.UNITS_COEFFICIENT, 93);
//// Roll moment coefficient
- public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType("TYPE_ROLL_MOMENT_COEFF", "C\u03c4\u03a6", UnitGroup.UNITS_COEFFICIENT, 94);
+ public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), "C\u03c4\u03a6", UnitGroup.UNITS_COEFFICIENT, 94);
//// Roll forcing coefficient
- public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType("TYPE_ROLL_FORCING_COEFF", "Cf\u03a6", UnitGroup.UNITS_COEFFICIENT, 95);
+ public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), "Cf\u03a6", UnitGroup.UNITS_COEFFICIENT, 95);
//// Roll damping coefficient
- public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType("TYPE_ROLL_DAMPING_COEFF", "C\u03b6\u03a6", UnitGroup.UNITS_COEFFICIENT, 96);
+ public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), "C\u03b6\u03a6", UnitGroup.UNITS_COEFFICIENT, 96);
//// Pitch damping coefficient
- public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType("TYPE_PITCH_DAMPING_MOMENT_COEFF", "C\u03b6\u03b8", UnitGroup.UNITS_COEFFICIENT, 97);
+ public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), "C\u03b6\u03b8", UnitGroup.UNITS_COEFFICIENT, 97);
//// Yaw damping coefficient
- public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType("TYPE_YAW_DAMPING_MOMENT_COEFF", "C\u03b6\u03a8", UnitGroup.UNITS_COEFFICIENT, 98);
+ public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), "C\u03b6\u03a8", UnitGroup.UNITS_COEFFICIENT, 98);
//// Coriolis acceleration
- public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType("TYPE_CORIOLIS_ACCELERATION", "Ac", UnitGroup.UNITS_ACCELERATION, 99);
+ public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType(trans.get("FlightDataType.TYPE_CORIOLIS_ACCELERATION"), "Ac", UnitGroup.UNITS_ACCELERATION, 99);
//// Reference length + area
//// Reference length
- public static final FlightDataType TYPE_REFERENCE_LENGTH = newType("TYPE_REFERENCE_LENGTH", "Lr", UnitGroup.UNITS_LENGTH, 100);
+ public static final FlightDataType TYPE_REFERENCE_LENGTH = newType(trans.get("FlightDataType.TYPE_REFERENCE_LENGTH"), "Lr", UnitGroup.UNITS_LENGTH, 100);
//// Reference area
- public static final FlightDataType TYPE_REFERENCE_AREA = newType("TYPE_REFERENCE_AREA", "Ar", UnitGroup.UNITS_AREA, 101);
+ public static final FlightDataType TYPE_REFERENCE_AREA = newType(trans.get("FlightDataType.TYPE_REFERENCE_AREA"), "Ar", UnitGroup.UNITS_AREA, 101);
//// Orientation
//// Vertical orientation (zenith)
- public static final FlightDataType TYPE_ORIENTATION_THETA = newType("TYPE_ORIENTATION_THETA", "\u0398", UnitGroup.UNITS_ANGLE, 106);
+ public static final FlightDataType TYPE_ORIENTATION_THETA = newType(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), "\u0398", UnitGroup.UNITS_ANGLE, 106);
//// Lateral orientation (azimuth)
- public static final FlightDataType TYPE_ORIENTATION_PHI = newType("TYPE_ORIENTATION_PHI", "\u03a6", UnitGroup.UNITS_ANGLE, 107);
+ public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), "\u03a6", UnitGroup.UNITS_ANGLE, 107);
//// Atmospheric conditions
//// Wind velocity
- public static final FlightDataType TYPE_WIND_VELOCITY = newType("TYPE_WIND_VELOCITY", "Vw", UnitGroup.UNITS_VELOCITY, 110);
+ public static final FlightDataType TYPE_WIND_VELOCITY = newType(trans.get("FlightDataType.TYPE_WIND_VELOCITY"), "Vw", UnitGroup.UNITS_VELOCITY, 110);
//// Air temperature
- public static final FlightDataType TYPE_AIR_TEMPERATURE = newType("TYPE_AIR_TEMPERATURE", "T", UnitGroup.UNITS_TEMPERATURE, 111);
+ public static final FlightDataType TYPE_AIR_TEMPERATURE = newType(trans.get("FlightDataType.TYPE_AIR_TEMPERATURE"), "T", UnitGroup.UNITS_TEMPERATURE, 111);
//// Air pressure
- public static final FlightDataType TYPE_AIR_PRESSURE = newType("TYPE_AIR_PRESSURE", "p", UnitGroup.UNITS_PRESSURE, 112);
+ public static final FlightDataType TYPE_AIR_PRESSURE = newType(trans.get("FlightDataType.TYPE_AIR_PRESSURE"), "p", UnitGroup.UNITS_PRESSURE, 112);
//// Speed of sound
- public static final FlightDataType TYPE_SPEED_OF_SOUND = newType("TYPE_SPEED_OF_SOUND", "Vs", UnitGroup.UNITS_VELOCITY, 113);
+ public static final FlightDataType TYPE_SPEED_OF_SOUND = newType(trans.get("FlightDataType.TYPE_SPEED_OF_SOUND"), "Vs", UnitGroup.UNITS_VELOCITY, 113);
//// Simulation information
//// Simulation time step
- public static final FlightDataType TYPE_TIME_STEP = newType("TYPE_TIME_STEP", "dt", UnitGroup.UNITS_TIME_STEP, 200);
+ public static final FlightDataType TYPE_TIME_STEP = newType(trans.get("FlightDataType.TYPE_TIME_STEP"), "dt", UnitGroup.UNITS_TIME_STEP, 200);
//// Computation time
- public static final FlightDataType TYPE_COMPUTATION_TIME = newType("TYPE_COMPUTATION_TIME", "tc", UnitGroup.UNITS_SHORT_TIME, 201);
+ public static final FlightDataType TYPE_COMPUTATION_TIME = newType(trans.get("FlightDataType.TYPE_COMPUTATION_TIME"), "tc", UnitGroup.UNITS_SHORT_TIME, 201);
// An array of all the built in types
public static final FlightDataType[] ALL_TYPES = {
};
/**
- * Return a {@link FlightDataType} based on a string description. This returns known data types
- * if possible, or a new type otherwise.
+ * Return a {@link FlightDataType} with a given string description, symbol and unitgroup.
+ * This returns an existing data type if the symbol matches that of an existing type.
+ *
+ * If the symbol matches but the unit and description information differ, then the old stored datatype
+ * is erased and the updated version based on the given parametes is returned.
+ * The only exception is if the description or unitgroup are undefined (null or empty string). In this case
+ * we just get these parameters from the existing type when making the new one.
*
* @param s the string description of the type.
* @param u the unit group the new type should belong to if a new group is created.
* @return a data type.
*/
+ @SuppressWarnings("null")
public static synchronized FlightDataType getType(String s, String symbol, UnitGroup u) {
- // modified to include the unit
- FlightDataType type = EXISTING_TYPES.get(s.toLowerCase(Locale.ENGLISH));
+
+ // if symbol is null : try finding by name
+ // if unit is null : don't do anything to the unit if found, just return datatype if found and generate an error and an empty unit otherwise
+ int oldPriority = DEFAULT_PRIORITY;
- // added this for backward compatibility. Will update type if symbol undefined
- //if (type != null && type.getSymbol() != symbol){
- // EXISTING_TYPES.remove(type);
- // type = null;
- //}
+ //FlightDataType type = findFromSymbol(symbol);
+ FlightDataType type = EXISTING_TYPES.get(symbol);
if (type != null) {
- return type;
+ // found it from symbol
+
+ // if name was not give (empty string), can use the one we found name
+ if ( s.equals("") || s == null ){
+ s = type.getName();
+ }
+ if ( u == null ){
+ u = type.getUnitGroup();
+ }
+
+ // if something has changed, then we need to remove the old one
+ // otherwise, just return what we found
+ if ( !u.equals(type.getUnitGroup()) ||
+ !s.equals(type.getName())
+ )
+ {
+ oldPriority = type.priority;
+
+ EXISTING_TYPES.remove(type);
+ log.info("Something changed with the type "+type.getName()+", removed old version.");
+ }
+ else{
+ return type;
+ }
}
- type = newType("UserDefined." + s, s, symbol, u, DEFAULT_PRIORITY);
- return type;
+
+ if (u == null){
+ u = UnitGroup.UNITS_NONE;
+ log.error("Made a new flightdatatype, but did not know what units to use.");
+ }
+
+ // make a new one
+ type = newType(s, symbol, u, oldPriority);
+ return type;
}
- /**
- * Used while initializing the class.
+ /*
+ * Get the flightdatatype from existing types based on the symbol.
*/
-
- private static FlightDataType newType( String key , String symbol, UnitGroup u, int priority ) {
- String name = trans.get("FlightDataType." + key );
- return newType( key, name, symbol, u, priority );
+ /*
+ private static FlightDataType findFromSymbol(String symbol){
+ for (FlightDataType t : EXISTING_TYPES.values()){
+ if (t.getSymbol().equals(symbol)){
+ return t;
+ }
+ }
+ return null;
}
+ */
- private static synchronized FlightDataType newType(String key, String s, String symbol, UnitGroup u, int priority) {
- FlightDataType type = new FlightDataType(key, s, symbol, u, priority);
- EXISTING_TYPES.put(s.toLowerCase(Locale.ENGLISH), type);
+ /**
+ * Used while initializing the class.
+ */
+ private static synchronized FlightDataType newType(String s, String symbol, UnitGroup u, int priority) {
+ FlightDataType type = new FlightDataType(s, symbol, u, priority);
+ //EXISTING_TYPES.put(s.toLowerCase(Locale.ENGLISH), type);
+ EXISTING_TYPES.put(symbol, type);
return type;
}
- private final String key;
+
private final String name;
private final String symbol;
private final UnitGroup units;
private final int hashCode;
- private FlightDataType(String key, String typeName, String symbol, UnitGroup units, int priority) {
- this.key = key;
+ private FlightDataType(String typeName, String symbol, UnitGroup units, int priority) {
if (typeName == null)
throw new IllegalArgumentException("typeName is null");
if (units == null)
this.symbol = symbol;
this.units = units;
this.priority = priority;
- this.hashCode = this.key.hashCode();
+ this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode();
}
/*
}
*/
- public String getKey() {
- return key;
- }
-
public String getName() {
return name;
}
public boolean equals(Object other) {
if (!(other instanceof FlightDataType))
return false;
- return this.hashCode == other.hashCode();
+ return this.name.equalsIgnoreCase(((FlightDataType) other).name);
}
@Override
--- /dev/null
+package net.sf.openrocket.simulation.customexpression;
+
+import java.util.List;
+import java.util.regex.*;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.SimulationStatus;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.FixedUnitGroup;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.ArrayList;
+import de.congrace.exp4j.Calculable;
+import de.congrace.exp4j.ExpressionBuilder;
+import de.congrace.exp4j.UnknownFunctionException;
+import de.congrace.exp4j.UnparsableExpressionException;
+import de.congrace.exp4j.Variable;
+
+/**
+ * Represents a single custom expression
+ * @author Richard Graham
+ *
+ */
+public class CustomExpression implements Cloneable{
+
+ private static final LogHelper log = Application.getLogger();
+
+ private OpenRocketDocument doc;
+ private String name, symbol, unit;
+
+ protected String expression;
+ private ExpressionBuilder builder;
+ private List<CustomExpression> subExpressions = new ArrayList<CustomExpression>();
+
+ public CustomExpression(OpenRocketDocument doc){
+ setName("");
+ setSymbol("");
+ setUnit("");
+ setExpression("");
+ this.doc = doc;
+ }
+
+ public CustomExpression(OpenRocketDocument doc,
+ String name,
+ String symbol,
+ String unit,
+ String expression) {
+ this.doc = doc;
+
+ setName(name);
+ setSymbol(symbol);
+ setUnit(unit);
+ setExpression(expression);
+ }
+
+ /*
+ * Sets the long name of this expression, e.g. 'Kinetic energy'
+ */
+ public void setName(String name){
+ this.name = name;
+ }
+
+ /*
+ * Sets the string for the units of the result of this expression.
+ */
+ public void setUnit(String unit){
+ this.unit = unit;
+ }
+
+ /*
+ * Sets the symbol string. This is the short, locale independent symbol for this whole expression
+ */
+ public void setSymbol(String symbol){
+ this.symbol = symbol;
+ }
+
+ /*
+ * Sets the actual expression string for this expression
+ */
+ public void setExpression(String expression){
+
+ // This is the expression as supplied
+ this.expression = expression;
+
+ // Replace any indexed variables
+ expression = subTimeIndexes(expression);
+ expression = subTimeRanges(expression);
+
+ builder = new ExpressionBuilder(expression);
+ for (String n : getAllSymbols()){
+ builder.withVariable(new Variable(n));
+ }
+ for (CustomExpression exp : this.subExpressions){
+ builder.withVariable(new Variable(exp.hash()));
+ }
+
+ builder.withCustomFunctions(Functions.getInstance().getAllFunction());
+ log.info("Built expression "+expression);
+ }
+
+ /*
+ * Replaces expressions of the form:
+ * a[x:y] with a hash and creates an associated RangeExpression from x to y
+ */
+ private String subTimeRanges(String str){
+
+ Pattern p = Pattern.compile(variableRegex()+"\\[[^\\]]*:.*?\\]");
+ Matcher m = p.matcher(str);
+
+ // for each match, make a new custom expression (in subExpressions) with a hashed name
+ // and replace the expression and variable in the original expression string with [hash].
+ while (m.find()){
+ String match = m.group();
+
+ int start = match.indexOf("[");
+ int end = match.indexOf("]");
+ int colon = match.indexOf(":");
+
+ String startTime = match.substring(start+1, colon);
+ String endTime = match.substring(colon+1, end);
+ String variableType = match.substring(0, start);
+
+ RangeExpression exp = new RangeExpression(doc, startTime, endTime, variableType);
+ subExpressions.add( exp );
+ str = str.replace(match, exp.hash());
+ }
+ return str;
+ }
+
+ /*
+ * Replaces expressions of the form
+ * a[x] with a hash and creates an associated IndexExpression with x
+ */
+ private String subTimeIndexes(String str){
+
+ // find any matches of the time-indexed variable notation, e.g. m[1.2] for mass at 1.2 sec
+ Pattern p = Pattern.compile(variableRegex()+"\\[[^:]*?\\]");
+ Matcher m = p.matcher(str);
+
+ // for each match, make a new custom expression (in subExpressions) with a hashed name
+ // and replace the expression and variable in the original expression string with [hash].
+ while (m.find()){
+ String match = m.group();
+ // just the index part (in the square brackets) :
+ String indexText = match.substring(match.indexOf("[")+1, match.length()-1);
+ // just the flight data type
+ String typeText = match.substring(0, match.indexOf("["));
+
+ // Do the replacement and add a corresponding new IndexExpression to the list
+ IndexExpression exp = new IndexExpression(doc, indexText, typeText);
+ subExpressions.add( exp );
+ str = str.replace(match, exp.hash());
+ }
+ return str;
+ }
+
+ /*
+ * Returns a string of the form (t|a| ... ) with all variable symbols available
+ * This is useful for regex evaluation
+ */
+ protected String variableRegex(){
+ String regex = "(";
+ for (String s : getAllSymbols()){
+ regex = regex + s + "|";
+ }
+ regex = regex.substring(0, regex.length()-1) + ")";
+ return regex;
+ }
+
+ // get a list of all the names of all the available variables
+ protected ArrayList<String> getAllNames(){
+ ArrayList<String> names = new ArrayList<String>();
+ for (FlightDataType type : FlightDataType.ALL_TYPES)
+ names.add(type.getName());
+
+ if (doc != null){
+ ArrayList<CustomExpression> expressions = doc.getCustomExpressions();
+ for (CustomExpression exp : expressions ){
+ if (exp != this)
+ names.add(exp.getName());
+ }
+ }
+ return names;
+ }
+
+ // get a list of all the symbols of the available variables ignoring this one
+ protected ArrayList<String> getAllSymbols(){
+ ArrayList<String> symbols = new ArrayList<String>();
+ for (FlightDataType type : FlightDataType.ALL_TYPES)
+ symbols.add(type.getSymbol());
+
+ if (doc != null){
+ for (CustomExpression exp : doc.getCustomExpressions() ){
+ if (exp != this)
+ symbols.add(exp.getSymbol());
+ }
+ }
+ return symbols;
+ }
+
+ public boolean checkSymbol(){
+ if (symbol.trim().isEmpty())
+ return false;
+
+ // No bad characters
+ for (char c : "0123456789.,()[]{}<>:#@%^&* ".toCharArray())
+ if (symbol.indexOf(c) != -1 )
+ return false;
+
+ // No operators (ignoring brackets)
+ for (String s : Functions.AVAILABLE_OPERATORS.keySet()){
+ if (symbol.equals(s.trim().replaceAll("\\(|\\)|\\]|\\[|:", "")))
+ return false;
+ }
+
+ // No already defined symbols
+ ArrayList<String> symbols = getAllSymbols().clone();
+ if (symbols.contains(symbol.trim())){
+ int index = symbols.indexOf(symbol.trim());
+ log.user("Symbol "+symbol+" already exists, found "+symbols.get(index));
+ return false;
+ }
+
+ return true;
+ }
+
+ public boolean checkName(){
+ if (name.trim().isEmpty())
+ return false;
+
+ // No characters that could mess things up saving etc
+ for (char c : ",()[]{}<>#".toCharArray())
+ if (name.indexOf(c) != -1 )
+ return false;
+
+ ArrayList<String> names = getAllNames().clone();
+ if (names.contains(name.trim())){
+ int index = names.indexOf(name.trim());
+ log.user("Name "+name+" already exists, found "+names.get(index));
+ return false;
+ }
+
+ return true;
+ }
+
+ // Currently no restrictions on unit
+ public boolean checkUnit(){
+ return true;
+ }
+
+ public boolean checkAll(){
+ return checkUnit() && checkSymbol() && checkName() && checkExpression();
+ }
+
+ public String getName(){
+ return name;
+ }
+
+ public String getSymbol(){
+ return symbol;
+ }
+
+ public String getUnit(){
+ return unit;
+ }
+
+ public String getExpressionString(){
+ return expression;
+ }
+
+ /**
+ * Performs a basic check to see if the current expression string is valid
+ * This includes checking for bad characters and balanced brackets and test
+ * building the expression.
+ */
+ public boolean checkExpression(){
+ if (expression.trim().isEmpty()){
+ return false;
+ }
+
+ int round = 0, square = 0; // count of bracket openings
+ for (char c : expression.toCharArray()){
+ switch (c) {
+ case '(' : round++; break;
+ case ')' : round--; break;
+ case '[' : square++; break;
+ case ']' : square--; break;
+ case ':' :
+ if (square <= 0){
+ log.user(": found outside range expression");
+ return false;
+ }
+ else break;
+ case '#' : return false;
+ case '=' : return false;
+ }
+ }
+ if (round != 0 || square != 0) {
+ log.user("Expression has unballanced brackets");
+ return false;
+ }
+
+
+ //// Define the available variables as empty
+ // The built in data types
+ for (FlightDataType type : FlightDataType.ALL_TYPES){
+ builder.withVariable(new Variable(type.getSymbol()));
+ }
+
+ for (String symb : getAllSymbols()){
+ builder.withVariable(new Variable(symb));
+ }
+
+ // Try to build
+ try {
+ builder.build();
+ } catch (Exception e) {
+ log.user("Custom expression invalid : " + e.toString());
+ return false;
+ }
+
+
+ // Otherwise, all OK
+ return true;
+ }
+
+ public Double evaluateDouble(SimulationStatus status){
+ return evaluate(status).getDoubleValue();
+ }
+
+ /*
+ * Builds the expression, done automatically during evaluation. Logs any errors. Returns null in case of error.
+ */
+ protected Calculable buildExpression(){
+ return buildExpression(builder);
+ }
+
+ /*
+ * Builds a specified expression, log any errors and returns null in case of error.
+ */
+ protected Calculable buildExpression(ExpressionBuilder b){
+ Calculable calc;
+ try {
+ calc = b.build();
+ } catch (UnknownFunctionException e1) {
+ log.user("Unknown function. Could not build custom expression "+name);
+ return null;
+ } catch (UnparsableExpressionException e1) {
+ log.user("Unparsable expression. Could not build custom expression "+name+". "+e1.getMessage());
+ return null;
+ }
+
+ return calc;
+ }
+
+ /*
+ * Evaluate the expression using the last variable values from the simulation status.
+ * Returns NaN on any error.
+ */
+ public Variable evaluate(SimulationStatus status){
+
+ Calculable calc = buildExpression(builder);
+ if (calc == null){
+ return new Variable("Unknown");
+ }
+
+ // Evaluate any sub expressions and set associated variables in the calculable
+ for (CustomExpression expr : this.subExpressions){
+ calc.setVariable( expr.evaluate(status) );
+ }
+
+ // Set all the built-in variables. Strictly we surely won't need all of them
+ // Going through and checking them to include only the ones used *might* give a speedup
+ for (FlightDataType type : status.getFlightData().getTypes()){
+ double value = status.getFlightData().getLast(type);
+ calc.setVariable( new Variable(type.getSymbol(), value ) );
+ }
+
+ double result = Double.NaN;
+ try{
+ result = calc.calculate().getDoubleValue();
+ }
+ catch (java.util.EmptyStackException e){
+ log.user("Unable to calculate expression "+this.expression+" due to empty stack exception");
+ }
+
+ return new Variable(name, result);
+ }
+
+ /*
+ * Returns the new flight data type corresponding to this calculated data
+ * If the unit matches a SI unit string then the datatype will have the corresponding unitgroup.
+ * Otherwise, a fixed unit group will be created
+ */
+ public FlightDataType getType(){
+
+ UnitGroup ug = UnitGroup.SIUNITS.get(unit);
+ if ( ug == null ){
+ ug = new FixedUnitGroup(unit);
+ }
+
+ FlightDataType type = FlightDataType.getType(name, symbol, ug);
+
+ //log.debug(this.getClass().getSimpleName()+" returned type "+type.getName()+" (" + type.getSymbol() + ")" );
+
+ return type;
+ }
+
+ /*
+ * Add this expression to the document if valid and not in document already
+ */
+ public void addToDocument(){
+ // Abort if exact expression already in
+ ArrayList<CustomExpression> expressions = doc.getCustomExpressions();
+ if ( !expressions.isEmpty() ) {
+ // check if expression already exists
+ if ( expressions.contains(this) ){
+ log.user("Expression already in document. This unit : "+this.getUnit()+", existing unit : "+expressions.get(0).getUnit());
+ return;
+ }
+ }
+
+ if (this.checkAll()){
+ log.user("Custom expression added to rocket document");
+ doc.addCustomExpression( this );
+ }
+ }
+
+ /*
+ * Removes this expression from the document, replacing it with a given new expression
+ */
+ public void overwrite(CustomExpression newExpression){
+ if (!doc.getCustomExpressions().contains(this))
+ return;
+ else {
+ int index = doc.getCustomExpressions().indexOf(this);
+ doc.getCustomExpressions().set(index, newExpression);
+ }
+ }
+
+ @Override
+ public String toString(){
+ return "Custom expression : "+this.name.toString()+ " " + this.expression.toString();
+ }
+
+ @Override
+ /*
+ * Clone method makes a deep copy of everything except the reference to the document.
+ * If you want to apply this to another simulation, set simulation manually after cloning.
+ * @see java.lang.Object#clone()
+ */
+ public Object clone() {
+ try {
+ return super.clone();
+ }
+ catch( CloneNotSupportedException e )
+ {
+ return new CustomExpression( doc ,
+ new String(this.getName()),
+ new String(this.getSymbol()),
+ new String(this.getUnit()),
+ new String(this.getExpressionString()));
+ }
+ }
+
+ /*
+ * Returns a simple all upper case string hash code with a proceeding # mark.
+ * Used for temporary substitution when evaluating index and range expressions.
+ */
+ public String hash(){
+ Integer hashint = new Integer(this.getExpressionString().hashCode());
+ String hash = "#";
+ for (char c : hashint.toString().toCharArray()){
+ char newc = (char) (c + 17);
+ hash = hash + newc;
+ }
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj){
+ CustomExpression other = (CustomExpression) obj;
+
+ return ( this.getName().equals( other.getName() ) &&
+ this.getSymbol().equals( other.getSymbol() ) &&
+ this.getExpressionString().equals( other.getExpressionString() ) &&
+ this.getUnit().equals( other.getUnit() )
+ );
+ }
+
+ @Override
+ public int hashCode() {
+ return hash().hashCode();
+ }
+}
--- /dev/null
+package net.sf.openrocket.simulation.customexpression;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayUtils;
+
+import de.congrace.exp4j.CustomFunction;
+import de.congrace.exp4j.InvalidCustomFunctionException;
+import de.congrace.exp4j.Variable;
+
+/*
+ * This is a singleton class which contains all the functions for custom expressions not provided by exp4j
+ */
+public class Functions {
+ private static Functions instance = null;
+
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+ private List<CustomFunction> allFunctions = new ArrayList<CustomFunction>();
+
+ public static Functions getInstance() {
+ if(instance == null) {
+ try {
+ instance = new Functions();
+ } catch (InvalidCustomFunctionException e) {
+ log.error("Invalid custom function.");
+ }
+ }
+ return instance;
+ }
+
+ public List<CustomFunction> getAllFunction(){
+ return allFunctions;
+ }
+
+ // A map of available operator strings (keys) and description of function (value)
+ public static final SortedMap<String, String> AVAILABLE_OPERATORS = new TreeMap<String, String>() {{
+ put("+" , trans.get("Operator.plus"));
+ put("-" , trans.get("Operator.minus"));
+ put("*" , trans.get("Operator.star"));
+ put("/" , trans.get("Operator.div"));
+ put("%" , trans.get("Operator.mod"));
+ put("^" , trans.get("Operator.pow"));
+ put("abs()" , trans.get("Operator.abs"));
+ put("ceil()" , trans.get("Operator.ceil"));
+ put("floor()" , trans.get("Operator.floor"));
+ put("sqrt()" , trans.get("Operator.sqrt"));
+ put("cbrt()" , trans.get("Operator.cbrt"));
+ put("exp()" , trans.get("Operator.exp"));
+ put("log()" , trans.get("Operator.ln"));
+ put("sin()" , trans.get("Operator.sin"));
+ put("cos()" , trans.get("Operator.cos"));
+ put("tan()" , trans.get("Operator.tan"));
+ put("asin()" , trans.get("Operator.asin"));
+ put("acos()" , trans.get("Operator.acos"));
+ put("atan()" , trans.get("Operator.atan"));
+ put("sinh()" , trans.get("Operator.hsin"));
+ put("cosh()" , trans.get("Operator.hcos"));
+ put("tanh()" , trans.get("Operator.htan"));
+ put("log10()" , trans.get("Operator.log10"));
+ put("round()" , trans.get("Operator.round"));
+ put("random()" , trans.get("Operator.random"));
+ put("expm1()" , trans.get("Operator.expm1"));
+ put("mean([:])" , trans.get("Operator.mean"));
+ put("min([:])" , trans.get("Operator.min"));
+ put("max([:])" , trans.get("Operator.max"));
+ put("var([:])" , trans.get("Operator.var"));
+ put("rms([:])" , trans.get("Operator.rms"));
+ put("stdev([:])", trans.get("Operator.stdev"));
+ put("lclip(,)" , trans.get("Operator.lclip"));
+ put("uclip(,)" , trans.get("Operator.uclip"));
+ put("binf([:],,)" , trans.get("Operator.binf"));
+ put("trapz([:])" , trans.get("Operator.trapz"));
+ put("tnear([:],)" , trans.get("Operator.tnear"));
+ }};
+
+
+ protected Functions() throws InvalidCustomFunctionException {
+
+ CustomFunction meanFn = new CustomFunction("mean") {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] vals;
+ try{
+ vals = vars.get(0).getArrayValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+ return new Variable("double MEAN result, ", ArrayUtils.mean(vals));
+ }
+ };
+ allFunctions.add(meanFn);
+
+ CustomFunction minFn = new CustomFunction("min") {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] vals;
+ try{
+ vals = vars.get(0).getArrayValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+ return new Variable("double MIN result, ", ArrayUtils.min(vals));
+ }
+ };
+ allFunctions.add(minFn);
+
+ CustomFunction maxFn = new CustomFunction("max") {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] vals;
+ try{
+ vals = vars.get(0).getArrayValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+ return new Variable("double MAX result, ", ArrayUtils.max(vals));
+ }
+ };
+ allFunctions.add(maxFn);
+
+ CustomFunction varFn = new CustomFunction("var") {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] vals;
+ try{
+ vals = vars.get(0).getArrayValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+ return new Variable("double VAR result, ", ArrayUtils.variance(vals));
+ }
+ };
+ allFunctions.add(varFn);
+
+ CustomFunction stdevFn = new CustomFunction("stdev") {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] vals;
+ try{
+ vals = vars.get(0).getArrayValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+ return new Variable("double STDEV result, ", ArrayUtils.stdev(vals));
+ }
+ };
+ allFunctions.add(stdevFn);
+
+ CustomFunction rmsFn = new CustomFunction("rms") {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] vals;
+ try{
+ vals = vars.get(0).getArrayValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+ return new Variable("double RMS result, ", ArrayUtils.rms(vals));
+ }
+ };
+ allFunctions.add(rmsFn);
+
+ CustomFunction lclipFn = new CustomFunction("lclip",2) {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double val, clip;
+ try{
+ val = vars.get(0).getDoubleValue();
+ clip = vars.get(1).getDoubleValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+ if (val < clip){
+ val = clip;
+ }
+ return new Variable("double LCLIP result, ", val);
+ }
+ };
+ allFunctions.add(lclipFn);
+
+ CustomFunction uclipFn = new CustomFunction("uclip",2) {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double val, clip;
+ try{
+ val = vars.get(0).getDoubleValue();
+ clip = vars.get(1).getDoubleValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+ if (val > clip){
+ val = clip;
+ }
+ return new Variable("double UCLIP result, ", val);
+ }
+ };
+ allFunctions.add(uclipFn);
+
+ CustomFunction binfFn = new CustomFunction("binf", 3) {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] range;
+ double min, max;
+ try{
+ range = vars.get(0).getArrayValue();
+ min = vars.get(1).getDoubleValue();
+ max = vars.get(2).getDoubleValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+
+ int ins = 0;
+ for (double x: range){
+ if (x < max && x > min){
+ ins++;
+ }
+ }
+ return new Variable("double BINF result", (double) ins/ (double) range.length);
+ }
+ };
+ allFunctions.add(binfFn);
+
+ CustomFunction rombintFn = new CustomFunction("trapz") {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] range;
+ double dt = 0;
+ try{
+ range = vars.get(0).getArrayValue();
+ dt = vars.get(0).getStep();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+
+ return new Variable("double TRAPZ result", ArrayUtils.trapz(range, dt) );
+ }
+ };
+ allFunctions.add(rombintFn);
+
+ CustomFunction tnearFn = new CustomFunction("tnear", 2) {
+ @Override
+ public Variable applyFunction(List<Variable> vars) {
+ double[] range;
+ double dt = 0;
+ double start = 0;
+ double near = 0;
+ try{
+ range = vars.get(0).getArrayValue();
+ dt = vars.get(0).getStep();
+ start = vars.get(0).getStart();
+ near = vars.get(1).getDoubleValue();
+ } catch (Exception e) {
+ return new Variable("Invalid");
+ }
+
+ return new Variable("double TNEAR result", ArrayUtils.tnear(range, near, start, dt) );
+ }
+ };
+ allFunctions.add(tnearFn);
+ }
+}
--- /dev/null
+package net.sf.openrocket.simulation.customexpression;
+
+import java.util.List;
+
+import de.congrace.exp4j.Calculable;
+import de.congrace.exp4j.Variable;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.SimulationStatus;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.LinearInterpolator;
+
+public class IndexExpression extends CustomExpression {
+
+ FlightDataType type;
+ private static final LogHelper log = Application.getLogger();
+
+ public IndexExpression(OpenRocketDocument doc, String indexText, String typeText){
+ super(doc);
+
+ setExpression(indexText);
+ this.setName("");
+ this.setSymbol(typeText);
+
+ }
+
+ @Override
+ public Variable evaluate(SimulationStatus status){
+
+ Calculable calc = buildExpression();
+ if (calc == null){
+ return new Variable("Unknown");
+ }
+
+ // From the given datatype, get the time and function values and make an interpolator
+ FlightDataType type = getType();
+ List<Double> data = status.getFlightData().get(type);
+ List<Double> time = status.getFlightData().get(FlightDataType.TYPE_TIME);
+ LinearInterpolator interp = new LinearInterpolator(time, data);
+
+ // Evaluate this expression to get the t value
+ try{
+ double tvalue = calc.calculate().getDoubleValue();
+ return new Variable(hash(), interp.getValue( tvalue ) );
+ }
+ catch (java.util.EmptyStackException e){
+ log.user("Unable to calculate time index for indexed expression "+getExpressionString()+" due to empty stack exception");
+ return new Variable("Unknown");
+ }
+
+ }
+}
--- /dev/null
+/*
+ * A range expression contains two indexExpressions for the beginning and end time index of a range
+ */
+
+package net.sf.openrocket.simulation.customexpression;
+
+import java.util.List;
+
+import de.congrace.exp4j.Calculable;
+import de.congrace.exp4j.ExpressionBuilder;
+import de.congrace.exp4j.Variable;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.SimulationStatus;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayUtils;
+import net.sf.openrocket.util.LinearInterpolator;
+import net.sf.openrocket.util.MathUtil;
+
+public class RangeExpression extends CustomExpression {
+ private static final LogHelper log = Application.getLogger();
+
+ private ExpressionBuilder startBuilder, endBuilder;
+
+ public RangeExpression(OpenRocketDocument doc, String startTime, String endTime, String variableType) {
+ super(doc);
+
+ if (startTime.isEmpty()){
+ startTime = "0";
+ }
+ if (endTime.isEmpty()){
+ endTime = "t";
+ }
+
+ this.setName("");
+ this.setSymbol(variableType);
+ this.setExpressions(startTime, endTime);
+ this.expression = variableType+startTime+endTime; // this is used just for generating the hash
+
+ log.info("New range expression, "+startTime + " to "+endTime);
+
+ }
+
+ /*
+ * Sets the actual expression string for this expression
+ */
+ private void setExpressions(String start, String end){
+
+ startBuilder = new ExpressionBuilder(start);
+ endBuilder = new ExpressionBuilder(end);
+ for (String n : getAllSymbols()){
+ startBuilder.withVariable(new Variable(n));
+ endBuilder.withVariable(new Variable(n));
+ }
+ }
+
+ @Override
+ public Variable evaluate(SimulationStatus status){
+
+ Calculable startCalc = buildExpression(startBuilder);
+ Calculable endCalc = buildExpression(endBuilder);
+ if (startCalc == null || endCalc == null){
+ return new Variable("Unknown");
+ }
+
+ // Set the variables in the start and end calculators
+ for (FlightDataType type : status.getFlightData().getTypes()){
+ double value = status.getFlightData().getLast(type);
+ startCalc.setVariable( new Variable(type.getSymbol(), value ) );
+ endCalc.setVariable( new Variable(type.getSymbol(), value ) );
+ }
+
+ // From the given datatype, get the time and function values and make an interpolator
+ FlightDataType type = getType();
+
+ List<Double> data = status.getFlightData().get(type);
+ List<Double> time = status.getFlightData().get(FlightDataType.TYPE_TIME);
+ LinearInterpolator interp = new LinearInterpolator(time, data);
+
+ // Evaluate the expression to get the start and end of the range
+ double startTime, endTime;
+ try{
+ startTime = startCalc.calculate().getDoubleValue();
+ startTime = MathUtil.clamp(startTime, 0, Double.MAX_VALUE);
+
+ endTime = endCalc.calculate().getDoubleValue();
+ endTime = MathUtil.clamp(endTime, 0, time.get(time.size()-1));
+ }
+ catch (java.util.EmptyStackException e){
+ log.user("Unable to calculate time index for range expression "+getSymbol()+" due to empty stack exception");
+ return new Variable("Unknown");
+ }
+
+ // generate an array representing the range
+ double step = status.getSimulationConditions().getSimulation().getOptions().getTimeStep();
+ double[] t = ArrayUtils.range(startTime, endTime, step);
+ double[] y = new double[t.length];
+ int i = 0;
+ for (double tval : t){
+ y[i] = interp.getValue( tval );
+ i++;
+ }
+
+ Variable result;
+ if (y.length == 0){
+ result = new Variable("Unknown");
+ }
+ else {
+ result = new Variable(hash(), y, startTime, step);
+ }
+
+ return result;
+ }
+}
return new GeneralUnit(1, unitString);
}
+ public Unit getSIUnit(){
+ return new GeneralUnit(1, unitString);
+ }
+
public boolean contains(Unit u){
return true;
}
public static final UnitGroup UNITS_MOTOR_DIMENSIONS;
public static final UnitGroup UNITS_LENGTH;
+ public static final UnitGroup UNITS_ALL_LENGTHS;
public static final UnitGroup UNITS_DISTANCE;
public static final UnitGroup UNITS_AREA;
public static final UnitGroup UNITS_ROUGHNESS;
public static final UnitGroup UNITS_COEFFICIENT;
+ public static final UnitGroup UNITS_FREQUENCY;
- // public static final UnitGroup UNITS_FREQUENCY;
+ public static final UnitGroup UNITS_ENERGY;
+ public static final UnitGroup UNITS_POWER;
+ public static final UnitGroup UNITS_MOMENTUM;
+ public static final UnitGroup UNITS_VOLTAGE;
+ public static final UnitGroup UNITS_CURRENT;
- public static final Map<String, UnitGroup> UNITS;
+ public static final Map<String, UnitGroup> UNITS; // keys such as "LENGTH", "VELOCITY"
+ public static final Map<String, UnitGroup> SIUNITS; // keys such a "m", "m/s"
/*
UNITS_NONE = new UnitGroup();
UNITS_NONE.addUnit(Unit.NOUNIT2);
+ UNITS_ENERGY = new UnitGroup();
+ UNITS_ENERGY.addUnit(new GeneralUnit(1, "J"));
+ UNITS_ENERGY.addUnit(new GeneralUnit(1e-7, "erg"));
+ UNITS_ENERGY.addUnit(new GeneralUnit(1.055, "BTU"));
+ UNITS_ENERGY.addUnit(new GeneralUnit(4.184, "cal"));
+ UNITS_ENERGY.addUnit(new GeneralUnit(1.3558179483314, "ft"+DOT+"lbf"));
+ UNITS_ENERGY.setDefaultUnit(0);
+
+ UNITS_POWER = new UnitGroup();
+ UNITS_POWER.addUnit(new GeneralUnit(1e-3, "mW"));
+ UNITS_POWER.addUnit(new GeneralUnit(1, "W"));
+ UNITS_POWER.addUnit(new GeneralUnit(1e3, "kW"));
+ UNITS_POWER.addUnit(new GeneralUnit(1e-7, "ergs"));
+ UNITS_POWER.addUnit(new GeneralUnit(745.699872, "hp"));
+ UNITS_POWER.setDefaultUnit(1);
+
+ UNITS_MOMENTUM = new UnitGroup();
+ UNITS_MOMENTUM.addUnit(new GeneralUnit(1, "kg"+DOT+"m/s"));
+ UNITS_MOMENTUM.setDefaultUnit(0);
+
+ UNITS_VOLTAGE = new UnitGroup();
+ UNITS_VOLTAGE.addUnit(new GeneralUnit(1e-3, "mV"));
+ UNITS_VOLTAGE.addUnit(new GeneralUnit(1, "V"));
+ UNITS_VOLTAGE.setDefaultUnit(1);
+
+ UNITS_CURRENT = new UnitGroup();
+ UNITS_CURRENT.addUnit(new GeneralUnit(1e-3, "mA"));
+ UNITS_CURRENT.addUnit(new GeneralUnit(1, "A"));
+ UNITS_CURRENT.setDefaultUnit(1);
+
UNITS_LENGTH = new UnitGroup();
UNITS_LENGTH.addUnit(new GeneralUnit(0.001, "mm"));
UNITS_LENGTH.addUnit(new GeneralUnit(0.01, "cm"));
UNITS_LENGTH.setDefaultUnit(1);
UNITS_MOTOR_DIMENSIONS = new UnitGroup();
+ UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(1, "m")); // just added
UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001, "mm"));
UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01, "cm"));
UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254, "in"));
UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344, "mi"));
UNITS_DISTANCE.addUnit(new GeneralUnit(1852, "nmi"));
+ UNITS_ALL_LENGTHS = new UnitGroup();
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.001, "mm"));
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.01, "cm"));
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1, "m"));
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1000, "km"));
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.0254, "in"));
+ UNITS_ALL_LENGTHS.addUnit(new FractionalUnit(0.0254, "in/64", "in", 64, 1d / 16d, 0.5d / 64d));
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.3048, "ft"));
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.9144, "yd"));
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1609.344, "mi"));
+ UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1852, "nmi"));
+ UNITS_ALL_LENGTHS.setDefaultUnit(2);
+
UNITS_AREA = new UnitGroup();
UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001), "mm" + SQUARED));
UNITS_AREA.addUnit(new GeneralUnit(pow2(0.01), "cm" + SQUARED));
UNITS_STABILITY = new UnitGroup();
+ UNITS_STABILITY.addUnit(new GeneralUnit(1, "m"));
UNITS_STABILITY.addUnit(new GeneralUnit(0.001, "mm"));
UNITS_STABILITY.addUnit(new GeneralUnit(0.01, "cm"));
UNITS_STABILITY.addUnit(new GeneralUnit(0.0254, "in"));
UNITS_ROUGHNESS = new UnitGroup();
+ UNITS_ROUGHNESS.addUnit(new GeneralUnit(1, "m")); // just added
UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, MICRO + "m"));
UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.0000254, "mil"));
// This is not used by OpenRocket, and not extensively tested:
- // UNITS_FREQUENCY = new UnitGroup();
+ UNITS_FREQUENCY = new UnitGroup();
// UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s"));
// UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms"));
// UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s"));
- // UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz"));
- // UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz"));
- // UNITS_FREQUENCY.setDefaultUnit(3);
+ UNITS_FREQUENCY.addUnit(new FrequencyUnit(.001, "mHz"));
+ UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz"));
+ UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz"));
+ UNITS_FREQUENCY.setDefaultUnit(1);
HashMap<String, UnitGroup> map = new HashMap<String, UnitGroup>();
map.put("NONE", UNITS_NONE);
map.put("LENGTH", UNITS_LENGTH);
+ map.put("ALL_LENGTHS", UNITS_ALL_LENGTHS);
map.put("MOTOR_DIMENSIONS", UNITS_MOTOR_DIMENSIONS);
map.put("DISTANCE", UNITS_DISTANCE);
map.put("VELOCITY", UNITS_VELOCITY);
map.put("RELATIVE", UNITS_RELATIVE);
map.put("ROUGHNESS", UNITS_ROUGHNESS);
map.put("COEFFICIENT", UNITS_COEFFICIENT);
+ map.put("VOLTAGE", UNITS_VOLTAGE);
+ map.put("CURRENT", UNITS_CURRENT);
+ map.put("ENERGY", UNITS_ENERGY);
+ map.put("POWER", UNITS_POWER);
+ map.put("MOMENTUM", UNITS_MOMENTUM);
+ map.put("FREQUENCY", UNITS_FREQUENCY);
UNITS = Collections.unmodifiableMap(map);
+
+ HashMap<String, UnitGroup> simap = new HashMap<String, UnitGroup>();
+ simap.put("m", UNITS_ALL_LENGTHS);
+ simap.put("m^2", UNITS_AREA);
+ simap.put("m/s", UNITS_VELOCITY);
+ simap.put("m/s^2", UNITS_ACCELERATION);
+ simap.put("kg", UNITS_MASS);
+ simap.put("kg m^2", UNITS_INERTIA);
+ simap.put("kg/m^3", UNITS_DENSITY_BULK);
+ simap.put("N", UNITS_FORCE);
+ simap.put("Ns", UNITS_IMPULSE);
+ simap.put("s", UNITS_FLIGHT_TIME);
+ simap.put("Pa", UNITS_PRESSURE);
+ simap.put("V", UNITS_VOLTAGE);
+ simap.put("A", UNITS_CURRENT);
+ simap.put("J", UNITS_ENERGY);
+ simap.put("W", UNITS_POWER);
+ simap.put("kg m/s", UNITS_MOMENTUM);
+ simap.put("Hz", UNITS_FREQUENCY);
+ simap.put("K", UNITS_TEMPERATURE);
+
+ SIUNITS = Collections.unmodifiableMap(simap);
}
public static void setDefaultMetricUnits() {
defaultUnit = n;
}
-
+ public Unit getSIUnit(){
+ for (Unit u : units){
+ if (u.multiplier == 1){
+ return u;
+ }
+ }
+ return UNITS_NONE.getDefaultUnit();
+ }
/**
* Find a unit by approximate unit name. Only letters and (ordinary) numbers are
return this.getDefaultUnit().toValue(value);
}
+ @Override
+ public String toString(){
+ return this.getClass().getSimpleName()+":"+this.getSIUnit().toString();
+ }
+ @Override
+ public boolean equals(Object o){
+ UnitGroup u = (UnitGroup) o;
+ int size = units.size();
+ if (size != u.units.size()){
+ return false;
+ }
+
+ for (int i=0; i<size; i++){
+ if ( !units.get(i).equals(u.units.get(i)) ){
+ return false;
+ }
+ }
+
+ return true;
+
+ }
private static final Pattern STRING_PATTERN = Pattern.compile("^\\s*([0-9.,-]+)(.*?)$");
///////////////////////////
+ @Override
+ public int hashCode() {
+ int code = 0;
+ for (Unit u : units){
+ code = code + u.hashCode();
+ }
+ return code;
+ }
+
/**
* A private class that switches the CaliberUnit to a rocket-specific CaliberUnit.
* All other methods are passed through to UNITS_STABILITY.
public class ArrayUtils {
+ /**
+ * Returns a double array with values from start to end with given step.
+ * Starts exactly at start and stops at the step before stop is reached.
+ */
+ public static double[] range(double start, double stop, double step){
+
+ int size = (int) Math.floor(((stop - start) / step));
+
+ //System.out.println("Range from "+start+" to "+stop+" step "+step+" has length "+size);
+
+ double[] output = new double[size];
+ int i = 0;
+ double x = start;
+ while (i<size){
+ output[i] = x;
+ x = x+step;
+ i++;
+ }
+
+ return output;
+ }
+
+ /**
+ * Return the mean of an array
+ */
+ public static double mean(double[] vals){
+ double subtotal = 0;
+ for (int i = 0; i < vals.length; i++ ){
+ subtotal += vals[i];
+ }
+ subtotal = subtotal / vals.length;
+ return subtotal;
+ }
+
+ /**
+ * Returns the maximum value in the array.
+ */
+
+ public static double max(double[] vals) {
+ double m = vals[0];
+ for (int i = 1; i < vals.length; i++)
+ m = Math.max(m, vals[i]);
+ return m;
+ }
+
+ /**
+ * Returns the minimum value in the array.
+ */
+
+ public static double min(double[] vals) {
+ double m = vals[0];
+ for (int i = 1; i < vals.length; i++)
+ m = Math.min(m, vals[i]);
+ return m;
+ }
+
+ /**
+ * Returns the variance of the array of doubles
+ */
+ public static double variance(double[] vals) {
+ double mu = mean(vals);
+ double sumsq = 0.0;
+ double temp = 0;
+ for (int i = 0; i < vals.length; i++){
+ temp = (mu - vals[i]);
+ sumsq += temp*temp;
+ }
+ return sumsq / (vals.length);
+ }
+
+ /**
+ * Returns the standard deviation of an array of doubles
+ */
+ public static double stdev(double[] vals) {
+ return Math.sqrt(variance(vals));
+ }
+
+ /**
+ * Returns the RMS value of an array of doubles
+ */
+ public static double rms(double[] vals) {
+ double m = mean(vals);
+ double s = stdev(vals);
+ return Math.sqrt( m*m + s*s );
+ }
+
+ /**
+ * Returns the integral of a given array calculated by the trapezoidal rule
+ * dt is the time step between each array value
+ */
+ public static double trapz(double[] y, double dt){
+ double stop = y.length * dt;
+
+ if (y.length <= 1 || dt <= 0) return 0;
+
+ double[] x = range(0, stop, dt);
+
+ double sum = 0.0;
+ for (int i = 1; i < x.length; i++) {
+ sum += (x[i] - x[i-1]) * (y[i] + y[i-1]);
+ }
+ return sum * 0.5;
+ }
+
+ /**
+ * Returns the nearest value in an array to a given value
+ * Search starts from the lowest array index
+ */
+ public static double tnear(double[] range, double near, double start, double step){
+ double min = Double.POSITIVE_INFINITY;
+ int mini = 0;
+
+ //System.out.println("Nearest to "+near+" in range length "+range.length);
+ for (int i=0; i < range.length; i++){
+ double x = Math.abs(range[i] - near);
+ if (x < min){
+ min = x;
+ mini = i;
+ }
+ }
+
+ //System.out.println("Found nearest at i="+mini);
+ return start + (mini*step);
+ }
+
+
public static <T> T[] copyOf( T[] original, int length ) {
return copyOfRange(original,0,length);
}
}
}
+
modified = modify(expression);
ExpressionBuilder builder = new ExpressionBuilder(modified);
Calculable calc = builder.build();
- double n = calc.calculate();
+ double n = calc.calculate().getDoubleValue();
log.debug("Evaluated expression '" + expression + "' (modified='" + modified + "') to " + n);
return n;
} catch (Exception e) {
package net.sf.openrocket.util;
+import java.util.Arrays;
import java.util.Iterator;
-import java.util.SortedMap;
+import java.util.List;
+import java.util.Map;
import java.util.TreeMap;
public class LinearInterpolator implements Cloneable {
*/
public LinearInterpolator() {
}
-
+
/**
* Construct a <code>LinearInterpolator</code> with the given points.
*
public LinearInterpolator(double[] x, double[] y) {
addPoints(x,y);
}
-
-
+
+ public LinearInterpolator(List<Double> x, List<Double> y) {
+ addPoints(x,y);
+ }
+
/**
* Add the point to the linear interpolation.
*
public void addPoint(double x, double y) {
sortMap.put(x, y);
}
-
+
/**
* Add the points to the linear interpolation.
*
sortMap.put(x[i],y[i]);
}
}
-
-
-
+
+ public void addPoints(List<Double> x, List<Double> y){
+ if (x.size() != y.size()) {
+ throw new IllegalArgumentException("Array lengths do not match, x="+x.size() +
+ " y="+y.size());
+ }
+ for (int i=0; i < x.size(); i++) {
+ sortMap.put( (Double) x.toArray()[i], (Double) y.toArray()[i]);
+ }
+ }
+
+
public double getValue(double x) {
+ Map.Entry<Double,Double> e1, e2;
double x1, x2;
- Double y1, y2;
- // Froyo does not support floorEntry, firstEntry or higherEntry. We instead have to
- // resort to using other more awkward methods.
-
- y1 = sortMap.get(x);
-
- if ( y1 != null ) {
- // Wow, x was a key in the map. Such luck.
- return y1.doubleValue();
- }
-
- // we now know that x is not in the map, so we need to find the lower and higher keys.
+ double y1, y2;
- // let's just make certain that our map is not empty.
- if ( sortMap.isEmpty() ) {
- throw new IllegalStateException("No points added yet to the interpolator.");
- }
+ e1 = sortMap.floorEntry(x);
- // firstKey in the map - cannot be null since the map is not empty.
- Double firstKey = sortMap.firstKey();
-
- // x is smaller than the first entry in the map.
- if ( x < firstKey.doubleValue() ) {
- y1 = sortMap.get(firstKey);
- return y1.doubleValue();
+ if (e1 == null) {
+ // x smaller than any value in the set
+ e1 = sortMap.firstEntry();
+ if (e1 == null) {
+ throw new IllegalStateException("No points added yet to the interpolator.");
+ }
+ return e1.getValue();
}
- // floor key is the largest key smaller than x - since we have at least one key,
- // and x>=firstKey, we know that floorKey != null.
- Double floorKey = sortMap.subMap(firstKey, x).lastKey();
-
- x1 = floorKey.doubleValue();
- y1 = sortMap.get(floorKey);
-
- // Now we need to find the key that is greater or equal to x
- SortedMap<Double,Double> tailMap = sortMap.tailMap(x);
+ x1 = e1.getKey();
+ e2 = sortMap.higherEntry(x1);
- // Check if x is bigger than all the entries.
- if ( tailMap.isEmpty() ) {
- return y1.doubleValue();
+ if (e2 == null) {
+ // x larger than any value in the set
+ return e1.getValue();
}
- Double ceilKey = tailMap.firstKey();
- // Check if x is bigger than all the entries.
- if ( ceilKey == null ) {
- return y1.doubleValue();
- }
+ x2 = e2.getKey();
+ y1 = e1.getValue();
+ y2 = e2.getValue();
- x2 = ceilKey.doubleValue();
- y2 = sortMap.get(ceilKey);
-
return (x - x1)/(x2-x1) * (y2-y1) + y1;
}
-
-
+
+
public double[] getXPoints() {
double[] x = new double[sortMap.size()];
Iterator<Double> iter = sortMap.keySet().iterator();
}
return x;
}
-
-
+
+
@SuppressWarnings("unchecked")
@Override
public LinearInterpolator clone() {
}
}
+
+ public static void main(String[] args) {
+ LinearInterpolator interpolator = new LinearInterpolator(
+ new double[] {1, 1.5, 2, 4, 5},
+ new double[] {0, 1, 0, 2, 2}
+ );
+
+ for (double x=0; x < 6; x+=0.1) {
+ System.out.printf("%.1f: %.2f\n", x, interpolator.getValue(x));
+ }
+
+ // Should be the same
+
+ ArrayList<Double> time = new ArrayList<Double>( Arrays.asList( new Double[] {1.0, 1.5, 2.0, 4.0, 5.0} ));
+ ArrayList<Double> y = new ArrayList<Double>( Arrays.asList( new Double[] {0.0, 1.0, 0.0, 2.0, 2.0} ));
+
+ LinearInterpolator interpolator2 = new LinearInterpolator(time,y);
+ for (double x=0; x < 6; x+=0.1) {
+ System.out.printf("%.1f: %.2f\n", x, interpolator2.getValue(x));
+ }
+ }
}
return sorted.get(n / 2).doubleValue();
}
}
-
+
/**
* Use interpolation to determine the value of the function at point t.
* Current implementation uses simple linear interpolation. The domain
s = s.replace(">", ">");
return s;
}
+
+ /*
+ * Returns a word-wrapped version of given input string using HTML syntax, wrapped to len characters.
+ */
+ public static String wrap(String in,int len) {
+ in=in.trim();
+ if(in.length()<len) return in;
+ if(in.substring(0, len).contains("\n"))
+ return in.substring(0, in.indexOf("\n")).trim() + "\n\n" + wrap(in.substring(in.indexOf("\n") + 1), len);
+ int place=Math.max(Math.max(in.lastIndexOf(" ",len),in.lastIndexOf("\t",len)),in.lastIndexOf("-",len));
+ return "<html>"+in.substring(0,place).trim()+"<br>"+wrap(in.substring(place),len);
+ }
+
}
--- /dev/null
+package net.sf.openrocket.simulation.customexpression;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.rocketcomponent.Rocket;
+
+public class TestExpressions {
+
+ @Test
+ public void testExpressions() {
+ // TODO Auto-generated constructor stub
+
+ OpenRocketDocument doc = new OpenRocketDocument(new Rocket());
+
+ //CustomExpression exp = new CustomExpression(doc, "Kinetic energy", "Ek", "J", ".5*m*Vt^2");
+
+ CustomExpression exp = new CustomExpression(doc, "Average mass", "Mavg", "kg", "mean(m[0:t])");
+ System.out.println( exp.getExpressionString() );
+
+ }
+}
public static final double EPS = 0.00000000001;
+ /*
+ @Test
+ public void rangeTest() {
+ double[] a;
+
+ a = MathUtil.range(0, 10, 2);
+ assertEquals(0, a[0], 0);
+ assertEquals(10, a[5], 0);
+ assertEquals(6, a.length, 0);
+
+ a = MathUtil.range(1, 2, 2);
+ assertEquals(1, a[0], 0);
+ assertEquals(1, a.length, 0);
+
+ }
+ */
+
@Test
public void miscMathTest() {