Big update to custom expression feature.

- supports range and index subexpressions and many new operators
 - switched to my patched version of exp4j to support all this.
 - expressions belong to rocket document. Accessed from analysis menu.
 - expression importing from file
 - datatypes section defined in file for storing datatypes other than internal ones
 - flightdatatype fix to forget outdated types
 - many GUI fixes to custom expressions
 - new unitgroups supported. Auto unit detection for SI units in custom expressions.

Had to carefully merge loading/saving code with Kevins recent de-localization update. Hopefully changes to materials saving kept but switched datatype access to just using symbol as the key.

Hopefully can get the changes to exp4j upstream so we don't need to keep using this patched version.
This commit is contained in:
Richard Graham 2012-08-05 23:59:54 +00:00
parent 48d094a1a9
commit 3d5ae6752a
34 changed files with 2092 additions and 868 deletions

View File

@ -40,4 +40,8 @@ The following file format versions exist:
<separationdelay> elements to stage components (except sustainer).
1.5: Introduced with OpenRocket 12.xx. Added ComponentPresets.
Added lowerstageseparation as recovery device deployment event.
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.

BIN
core/lib/exp4j-rdg.jar Normal file

Binary file not shown.

View File

@ -454,14 +454,18 @@ customExpression.Description = Description
! 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
@ -482,12 +486,12 @@ CustomOperatorSelector.title = Operator Selector
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)
@ -498,9 +502,24 @@ Operator.tan = Tangent
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
@ -1068,6 +1087,8 @@ main.menu.analyze.componentAnalysis = Component analysis
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

View File

@ -13,6 +13,7 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
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;
@ -50,7 +51,8 @@ public class OpenRocketDocument implements ComponentChangeListener {
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.*
@ -103,7 +105,22 @@ public class OpenRocketDocument implements ComponentChangeListener {
}
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;

View File

@ -13,7 +13,6 @@ import net.sf.openrocket.masscalc.MassCalculator;
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;
@ -72,7 +71,6 @@ public class Simulation implements ChangeSource, Cloneable {
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;
@ -161,21 +159,6 @@ public class Simulation implements ChangeSource, Cloneable {
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.
*

View File

@ -24,7 +24,7 @@ import net.sf.openrocket.rocketcomponent.RecoveryDevice.DeployEvent;
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;
@ -101,6 +101,9 @@ public class OpenRocketSaver extends RocketSaver {
writeln("");
// Save custom expressions;
saveCustomDatatypes(document);
// Save all simulations
writeln("<simulations>");
indent++;
@ -124,7 +127,37 @@ public class OpenRocketSaver extends RocketSaver {
}
}
/*
* 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) {
@ -327,20 +360,6 @@ public class OpenRocketSaver extends RocketSaver {
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());
@ -453,13 +472,16 @@ public class OpenRocketSaver extends RocketSaver {
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)

View File

@ -67,7 +67,7 @@ import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
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;
@ -649,6 +649,7 @@ class OpenRocketContentHandler extends AbstractElementHandler {
private boolean rocketDefined = false;
private boolean simulationsDefined = false;
private boolean datatypesDefined = false;
public OpenRocketContentHandler(DocumentLoadingContext context) {
this.context = context;
@ -656,7 +657,6 @@ class OpenRocketContentHandler extends AbstractElementHandler {
this.doc = new OpenRocketDocument(rocket);
}
public OpenRocketDocument getDocument() {
if (!rocketDefined)
return null;
@ -677,6 +677,15 @@ class OpenRocketContentHandler extends AbstractElementHandler {
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) {
@ -697,6 +706,90 @@ class OpenRocketContentHandler extends AbstractElementHandler {
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
@ -1211,9 +1304,7 @@ class SingleSimulationHandler extends AbstractElementHandler {
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) {
@ -1221,14 +1312,10 @@ class SingleSimulationHandler extends AbstractElementHandler {
this.context = context;
}
public void setCustomExpressions(ArrayList<CustomExpression> expressions){
this.customExpressions = expressions;
public OpenRocketDocument getDocument(){
return doc;
}
public ArrayList<CustomExpression> getCustomExpressions(){
return customExpressions;
}
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes,
WarningSet warnings) {
@ -1236,9 +1323,6 @@ class SingleSimulationHandler extends AbstractElementHandler {
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;
@ -1301,70 +1385,11 @@ class SingleSimulationHandler extends AbstractElementHandler {
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;
@ -1548,7 +1573,7 @@ class FlightDataHandler extends AbstractElementHandler {
private FlightDataBranchHandler dataHandler;
private WarningSet warningSet = new WarningSet();
private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
private SingleSimulationHandler simHandler;
private FlightData data;
@ -1575,9 +1600,8 @@ class FlightDataHandler extends AbstractElementHandler {
return null;
}
dataHandler = new FlightDataBranchHandler( attributes.get("name"),
attributes.get("typekeys"),
attributes.get("types"),
simHandler, context);
attributes.get("types"),
simHandler, context);
return dataHandler;
}
@ -1673,23 +1697,18 @@ class FlightDataBranchHandler extends AbstractElementHandler {
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());
}
@ -1697,13 +1716,14 @@ class FlightDataBranchHandler extends AbstractElementHandler {
// 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) ){
@ -1711,33 +1731,22 @@ class FlightDataBranchHandler extends AbstractElementHandler {
}
}
}
// 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()){
for (CustomExpression exp : simHandler.getDocument().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);
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);
}
@ -2069,133 +2078,134 @@ class ColorSetter implements Setter {
////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 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."));
}
List<ComponentPreset> presets = Application.getComponentPresetDao().find( manufacturerName, productNo );
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;
}
}
// Was any found?
if ( matchingPreset == null ) {
warnings.add(Warning.fromString("No matching ComponentPreset found " + manufacturerName + " " + productNo));
return;
}
if ( digest != null && !matchingPreset.getDigest().equals(digest) ) {
warnings.add(Warning.fromString("ComponentPreset has wrong digest"));
}
setMethod.invoke(c, matchingPreset);
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 type = attributes.get("type");
if ( type == null ) {
warnings.add(Warning.fromString("Invalid ComponentPreset, no type specified."));
}
List<ComponentPreset> presets = Application.getComponentPresetDao().find( manufacturerName, productNo );
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;
}
}
// Was any found?
if ( matchingPreset == null ) {
warnings.add(Warning.fromString("No matching ComponentPreset found " + manufacturerName + " " + productNo));
return;
}
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;
}
@Override
public void set(RocketComponent c, String name, HashMap<String, String> attributes,
WarningSet warnings) {
Material mat;
// 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 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;
}
String key = attributes.remove("key");
mat = Databases.findMaterial(type, key, name, density);
setMethod.invoke(c, mat);
}
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) {
Material mat;
// 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 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;
}
String key = attributes.remove("key");
mat = Databases.findMaterial(type, key, name, density);
setMethod.invoke(c, mat);
}
}

View File

@ -7,7 +7,6 @@ import java.util.Locale;
import net.sf.openrocket.rocketcomponent.ReferenceType;
import net.sf.openrocket.rocketcomponent.Rocket;
public class RocketSaver extends RocketComponentSaver {
private static final RocketSaver instance = new RocketSaver();
@ -22,8 +21,6 @@ public class RocketSaver extends RocketComponentSaver {
return list;
}
@Override
protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
super.addParams(c, elements);

View File

@ -0,0 +1,35 @@
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);
}
}

View File

@ -4,17 +4,28 @@ import java.awt.Color;
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;
@ -22,7 +33,8 @@ import net.sf.openrocket.gui.customexpression.ExpressionBuilderDialog;
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 {
@ -31,34 +43,91 @@ 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) {
this.add(button, "left");
//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);
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();
}
@ -69,19 +138,18 @@ public class CustomExpressionPanel extends JPanel {
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);
}
/**
@ -90,7 +158,7 @@ public class CustomExpressionPanel extends JPanel {
* @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;
@ -128,6 +196,9 @@ public class CustomExpressionPanel extends JPanel {
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"));
@ -136,7 +207,7 @@ public class CustomExpressionPanel extends JPanel {
@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();
}
});

View File

@ -19,11 +19,13 @@ import javax.swing.SwingConstants;
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;
/**
@ -44,7 +46,7 @@ public class ExpressionBuilderDialog extends JDialog {
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);
@ -53,16 +55,16 @@ public class ExpressionBuilderDialog extends JDialog {
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;
@ -159,7 +161,7 @@ public class ExpressionBuilderDialog extends JDialog {
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);
}
});
@ -174,21 +176,13 @@ public class ExpressionBuilderDialog extends JDialog {
}
});
//// 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();
@ -226,7 +220,6 @@ public class ExpressionBuilderDialog extends JDialog {
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");

View File

@ -1,14 +1,24 @@
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;
@ -16,6 +26,7 @@ import net.miginfocom.swing.MigLayout;
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 {
@ -24,24 +35,76 @@ 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);
@ -57,7 +120,7 @@ public class OperatorSelector extends JDialog {
}
});
mainPanel.add(scrollPane, "wrap");
mainPanel.add(scrollPane, "wrap, push, grow");
//// Cancel button
final JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
@ -73,10 +136,7 @@ public class OperatorSelector extends JDialog {
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
@ -87,4 +147,11 @@ public class OperatorSelector extends JDialog {
this.pack();
this.setLocationByPlatform(true);
}
private void selectOperator(){
int row = table.getSelectedRow();
String str = tableModel.getOperatorAt(row);
parentBuilder.pasteIntoExpression(str);
OperatorSelector.this.dispose();
}
}

View File

@ -3,7 +3,7 @@ package net.sf.openrocket.gui.customexpression;
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 {
@ -12,8 +12,8 @@ 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(){
@ -26,7 +26,7 @@ public class OperatorTableModel extends AbstractTableModel {
@Override
public int getRowCount() {
return CustomExpression.AVAILABLE_OPERATORS.size();
return Functions.AVAILABLE_OPERATORS.size();
}
@Override

View File

@ -3,19 +3,31 @@ package net.sf.openrocket.gui.customexpression;
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;
/**
@ -25,50 +37,82 @@ 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() {
@ -78,15 +122,12 @@ public class VariableSelector extends JDialog {
}
});
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
@ -97,4 +138,12 @@ public class VariableSelector extends JDialog {
this.pack();
this.setLocationByPlatform(true);
}
private void selectVariable(){
int row = table.getSelectedRow();
String str = tableModel.getSymbolAt(row);
parentBuilder.pasteIntoExpression(str);
VariableSelector.this.dispose();
}
}

View File

@ -3,15 +3,20 @@
*/
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;
@ -29,14 +34,13 @@ public class VariableTableModel extends AbstractTableModel {
/*
* 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
@ -56,7 +60,7 @@ public class VariableTableModel extends AbstractTableModel {
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;
}

View File

@ -11,6 +11,7 @@ import net.sf.openrocket.file.openrocket.OpenRocketSaver;
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;
@ -637,7 +638,7 @@ public class BasicFrame extends JFrame {
});
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() {
@ -649,7 +650,17 @@ public class BasicFrame extends JFrame {
});
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)

View File

@ -78,7 +78,7 @@ public class SimulationEditDialog extends JDialog {
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;
@ -139,8 +139,6 @@ public class SimulationEditDialog extends JDialog {
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
@ -150,7 +148,7 @@ public class SimulationEditDialog extends JDialog {
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)
@ -837,11 +835,6 @@ public class SimulationEditDialog extends JDialog {
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

View File

@ -42,7 +42,7 @@ public class Rocket extends RocketComponent {
* 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.
@ -121,9 +121,6 @@ public class Rocket extends RocketComponent {
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
/**
* Return the number of stages in this rocket.
*

View File

@ -18,6 +18,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
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;
@ -91,9 +92,9 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// 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

View File

@ -1,341 +0,0 @@
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()));
}
}
}

View File

@ -5,6 +5,7 @@ import java.util.Locale;
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;
@ -23,159 +24,160 @@ 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 = {
@ -235,46 +237,90 @@ public class FlightDataType implements Comparable<FlightDataType> {
};
/**
* 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;
}
/*
* Get the flightdatatype from existing types based on the symbol.
*/
/*
private static FlightDataType findFromSymbol(String symbol){
for (FlightDataType t : EXISTING_TYPES.values()){
if (t.getSymbol().equals(symbol)){
return t;
}
}
return null;
}
*/
/**
* Used while initializing the class.
*/
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 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);
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;
@ -282,8 +328,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
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)
@ -292,7 +337,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
this.symbol = symbol;
this.units = units;
this.priority = priority;
this.hashCode = this.key.hashCode();
this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode();
}
/*
@ -301,10 +346,6 @@ public class FlightDataType implements Comparable<FlightDataType> {
}
*/
public String getKey() {
return key;
}
public String getName() {
return name;
}
@ -326,7 +367,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
public boolean equals(Object other) {
if (!(other instanceof FlightDataType))
return false;
return this.hashCode == other.hashCode();
return this.name.equalsIgnoreCase(((FlightDataType) other).name);
}
@Override

View File

@ -0,0 +1,496 @@
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();
}
}

View File

@ -0,0 +1,269 @@
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);
}
}

View File

@ -0,0 +1,54 @@
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");
}
}
}

View File

@ -0,0 +1,116 @@
/*
* 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;
}
}

View File

@ -24,6 +24,10 @@ public class FixedUnitGroup extends UnitGroup {
return new GeneralUnit(1, unitString);
}
public Unit getSIUnit(){
return new GeneralUnit(1, unitString);
}
public boolean contains(Unit u){
return true;
}

View File

@ -27,6 +27,7 @@ public class UnitGroup {
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;
@ -63,11 +64,17 @@ public class UnitGroup {
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"
/*
@ -80,6 +87,36 @@ public class UnitGroup {
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"));
@ -90,6 +127,7 @@ public class UnitGroup {
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"));
@ -103,6 +141,19 @@ public class UnitGroup {
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));
@ -113,6 +164,7 @@ public class UnitGroup {
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"));
@ -234,6 +286,7 @@ public class UnitGroup {
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"));
@ -243,18 +296,20 @@ public class UnitGroup {
// 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);
@ -278,8 +333,36 @@ public class UnitGroup {
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() {
@ -393,7 +476,14 @@ public class UnitGroup {
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
@ -510,7 +600,28 @@ public class UnitGroup {
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.,-]+)(.*?)$");
@ -561,6 +672,15 @@ public class UnitGroup {
///////////////////////////
@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.

View File

@ -4,6 +4,132 @@ import java.lang.reflect.Array;
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);
}
@ -114,3 +240,4 @@ public class ArrayUtils {
}
}

View File

@ -25,7 +25,7 @@ public class ExpressionParser {
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) {

View File

@ -1,7 +1,9 @@
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 {
@ -14,7 +16,7 @@ public class LinearInterpolator implements Cloneable {
*/
public LinearInterpolator() {
}
/**
* Construct a <code>LinearInterpolator</code> with the given points.
*
@ -27,8 +29,11 @@ public class LinearInterpolator implements Cloneable {
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.
*
@ -38,7 +43,7 @@ public class LinearInterpolator implements Cloneable {
public void addPoint(double x, double y) {
sortMap.put(x, y);
}
/**
* Add the points to the linear interpolation.
*
@ -56,66 +61,50 @@ public class LinearInterpolator implements Cloneable {
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);
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();
}
// firstKey in the map - cannot be null since the map is not empty.
Double firstKey = sortMap.firstKey();
x1 = e1.getKey();
e2 = sortMap.higherEntry(x1);
// x is smaller than the first entry in the map.
if ( x < firstKey.doubleValue() ) {
y1 = sortMap.get(firstKey);
return y1.doubleValue();
if (e2 == null) {
// x larger than any value in the set
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);
// Check if x is bigger than all the entries.
if ( tailMap.isEmpty() ) {
return y1.doubleValue();
}
Double ceilKey = tailMap.firstKey();
x2 = e2.getKey();
y1 = e1.getValue();
y2 = e2.getValue();
// Check if x is bigger than all the entries.
if ( ceilKey == null ) {
return y1.doubleValue();
}
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();
@ -124,8 +113,8 @@ public class LinearInterpolator implements Cloneable {
}
return x;
}
@SuppressWarnings("unchecked")
@Override
public LinearInterpolator clone() {
@ -138,4 +127,25 @@ public class LinearInterpolator implements Cloneable {
}
}
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));
}
}
}

View File

@ -310,7 +310,7 @@ public class MathUtil {
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

View File

@ -184,4 +184,17 @@ public class TextUtil {
s = s.replace(">", "&gt;");
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);
}
}

View File

@ -0,0 +1,23 @@
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() );
}
}

View File

@ -13,6 +13,23 @@ public class MathUtilTest {
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() {