From 685fa98064d819d518323347255ee5e4410087af Mon Sep 17 00:00:00 2001 From: richardgraham Date: Sat, 2 Jun 2012 17:58:47 +0000 Subject: [PATCH] Added new feature : user configurable custom expression evaluation for the simulation, driven by exp4j. git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@738 180e2498-e6e9-4542-8430-84ac67f01cd8 --- core/resources/l10n/messages.properties | 36 +- core/resources/pix/icons/copyright.txt | 3 + core/resources/pix/icons/down.png | Bin 0 -> 912 bytes core/resources/pix/icons/pencil.png | Bin 0 -> 713 bytes core/resources/pix/icons/up.png | Bin 0 -> 906 bytes .../sf/openrocket/document/Simulation.java | 19 +- .../file/openrocket/OpenRocketSaver.java | 21 +- .../openrocket/importt/OpenRocketLoader.java | 76 ++++- .../CustomExpressionPanel.java | 195 +++++++++++ .../ExpressionBuilderDialog.java | 266 +++++++++++++++ .../customexpression/OperatorSelector.java | 90 +++++ .../customexpression/OperatorTableModel.java | 52 +++ .../customexpression/VariableSelector.java | 100 ++++++ .../customexpression/VariableTableModel.java | 77 +++++ .../gui/main/SimulationEditDialog.java | 11 +- .../src/net/sf/openrocket/gui/util/Icons.java | 4 + .../BasicEventSimulationEngine.java | 7 + .../simulation/CustomExpression.java | 317 ++++++++++++++++++ .../simulation/FlightDataBranch.java | 11 +- .../openrocket/simulation/FlightDataType.java | 182 ++++++---- .../simulation/SimulationConditions.java | 11 +- .../simulation/SimulationOptions.java | 6 +- .../example/RollControlListener.java | 3 +- .../sf/openrocket/unit/FixedUnitGroup.java | 31 ++ 24 files changed, 1435 insertions(+), 83 deletions(-) create mode 100644 core/resources/pix/icons/down.png create mode 100644 core/resources/pix/icons/pencil.png create mode 100644 core/resources/pix/icons/up.png create mode 100644 core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java create mode 100644 core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java create mode 100644 core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java create mode 100644 core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java create mode 100644 core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java create mode 100644 core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java create mode 100644 core/src/net/sf/openrocket/simulation/CustomExpression.java create mode 100644 core/src/net/sf/openrocket/unit/FixedUnitGroup.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index c7a472bb..2e5ba18c 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -89,7 +89,6 @@ dlg.but.ok = OK dlg.but.cancel = Cancel dlg.but.close = Close - ! General file type names filetypes.pdf = PDF files (*.pdf) BasicFrame.SimpleFileFilter1 = All rocket designs (*.ork; *.rkt) @@ -286,6 +285,7 @@ simedtdlg.lbl.Simname = Simulation name: simedtdlg.tab.Launchcond = Launch conditions simedtdlg.tab.Simopt = Simulation options simedtdlg.tab.Plotdata = Plot data +simedtdlg.tab.CustomExpressions = Custom expressions simedtdlg.tab.Exportdata = Export data simedtdlg.lbl.Motorcfg = Motor configuration: simedtdlg.lbl.ttip.Motorcfg = Select the motor configuration to use. @@ -444,6 +444,37 @@ CsvOptionPanel.separator.space = SPACE CsvOptionPanel.separator.tab = TAB +! Custom expression general stuff +customExpression.Name = Name +customExpression.Symbol = Symbol +customExpression.Expression = Expression +customExpression.Units = Units +customExpression.Operator = Operator +customExpression.Description = Description + +! Custom expression panel +customExpressionPanel.but.NewExpression = New expression +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 : +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 +ExpressionBuilderDialog.InsertOperator = Insert Operator +ExpressionBuilderDialog.led.ttip.Name = Name must not have already been used +ExpressionBuilderDialog.led.ttip.Symbol = Symbol must not have already been used +ExpressionBuilderDialog.led.ttip.Expression = Expression must use only known symbols and operators + +! Custom expression variable selector +CustomVariableSelector.title = Variable Selector + +! Custom operator selector +CustomOperatorSelector.title = Operator Selector ! MotorPlot MotorPlot.title.Motorplot = Motor plot @@ -457,8 +488,6 @@ MotorPlot.txt.Type = Type: MotorPlot.txt.Delays = Delays: MotorPlot.txt.Comment = Comment:\n - - ! Simulation plot panel simplotpanel.lbl.Presetplotconf = Preset plot configurations: simplotpanel.lbl.Xaxistype = X axis type: @@ -481,7 +510,6 @@ simplotpanel.CUSTOM = Custom SimulationPlotPanel.error.noPlotSelected = Please add one or more variables to plot on the Y-axis. SimulationPlotPanel.error.noPlotSelected.title = Nothing to plot - ! Component add buttons compaddbuttons.Bodycompandfinsets = Body components and fin sets compaddbuttons.Nosecone = Nose cone diff --git a/core/resources/pix/icons/copyright.txt b/core/resources/pix/icons/copyright.txt index 7320b950..809fdd68 100644 --- a/core/resources/pix/icons/copyright.txt +++ b/core/resources/pix/icons/copyright.txt @@ -20,6 +20,9 @@ edit-delete.png edit-paste.png edit-redo.png edit-undo.png +down.png +pencil.png +up.png edit-scale.png (modified from edit-copy.png) diff --git a/core/resources/pix/icons/down.png b/core/resources/pix/icons/down.png new file mode 100644 index 0000000000000000000000000000000000000000..f3bc4cd0932ce493d38a058dabf802a9c111277e GIT binary patch literal 912 zcmV;B18@9^P)yS03d+CF8KfNza+!=KOzi&{{Q^>^AGQ@-#~T${xUHB{Lb+I*LR?i>_Xk3g-D0RjkY03R>=HeDStS)iitj6ePei}15C-2C>F;m6nS3_pSR z``7;rJY1{{HbxQ_U;g~t%fZIXdE?pV*FdeB009Ixfa5=-P+O6;u&$~g!>`|e7{s_4 z8IB(Q!tm?+Payum@czR;21!9Nh8fM)?3`?jGH*ZsVF)SO4-N!?00M`>tCydXr!Bqo zTacHHVdJ%b4ADzIF>Jf^g@F<1e3m~y84g~4$Kcg)iJ^1lT?S!Z7KYiYZZbT0@G%pl z7a)Kjfyl;uV9omL^OkM7$>6BQ3Jka}47Zn{lswc@*9R=D^Z3+ z=if3cT6=3P12fZlkXC>IVuGfgKYtmS8VWm)-(mmtg`vTNo8kNC?+jmn`0JN%3?JWp zVW^3eV=&baU?}Q2`}F&-Ka~vZOg}+d0RjjSja)1*UOoFYj7?#gU7zO4wPMSq~h4HU(%) mB~T6{>jDH2Bc<6HAiw|t4?BIRMtdj#0000R)!_m*FeI9eJ0Gxq#H zf0_XxfS9lv%LmkKB8}kx16siJFV!*nd7pOS69ERhSDg(18DcA(#PsQ(DfoWDDC{a=@>nto>xWzb;o zP$*;Aa%nq5TlT(fKhM4o2P*mk(g_ei5CcqsHvff2=RY9k`aMC~Kn&o76a{330bLQrJ&U26`M=Sy3)uqGxWf%F0d5QMO1 v>|l^)2n34pz-1YUGXNlf7%9!p009O71V-RMzjpE=00000NkvXXu0mjfxvf2L literal 0 HcmV?d00001 diff --git a/core/resources/pix/icons/up.png b/core/resources/pix/icons/up.png new file mode 100644 index 0000000000000000000000000000000000000000..184c118b63424218e82ea1a7d8acf4aa7f4741dc GIT binary patch literal 906 zcmV;519kj~P)VFvnKmaj8 zi~&-tOdykhoPR+4yE`w}r0d|mFrNMU!#S#|tZIPb(;;$Ttx(Ls01!Z67cj6h12u#6 z{$*hJ`?okP)uedh1ZRd-cbOPI{a|2dZMA3k`-dU9v+L+fpew2wI9P#}GJ>@N1P}|{ zKoI!*$KJtNxoP?o7lu{0nHc7s{m=0I>ko$S@Bc8gwOcXVx%<9s?b_=X7+Bd>Gq6Go z002(O`0<@$42yR?WAJp7W(bQ^j|Upy3epM?06_r0{{a90{{R;znA6A1=l}Ql{Qva# z`~Uy@_y3f;LcX?f=u)<^LcpoZIoKfq2*hm6EDW#S{A4I=JM-uJ@4p{8IGH(qvOydV z5I|r9-hTMuwr<5$A%VDw!}=|E4={ZD zQ^df`#Ks`R@e!mKAb=nN!Nhp~A2-{51} simulationListeners = new ArrayList(); + private ArrayList customExpressions = new ArrayList(); private final Class simulationEngineClass = BasicEventSimulationEngine.class; private Class simulationStepperClass = RK4SimulationStepper.class; private Class aerodynamicCalculatorClass = BarrowmanCalculator.class; private Class massCalculatorClass = BasicMassCalculator.class; - - /** Listeners for this object */ private List listeners = new ArrayList(); @@ -148,6 +148,20 @@ public class Simulation implements ChangeSource, Cloneable { } + 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 getCustomExpressions(){ + return customExpressions; + } + /** * Return the rocket associated with this simulation. @@ -280,6 +294,7 @@ public class Simulation implements ChangeSource, Cloneable { } SimulationConditions simulationConditions = options.toSimulationConditions(); + simulationConditions.setSimulation(this); for (SimulationListener l : additionalListeners) { simulationConditions.getSimulationListenerList().add(l); } diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 0525a4ed..ac061150 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -24,6 +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.FlightData; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; @@ -322,10 +323,25 @@ public class OpenRocketSaver extends RocketSaver { writeln("" + escapeXML(simulation.getName()) + ""); // TODO: MEDIUM: Other simulators/calculators + writeln("RK4Simulator"); writeln("BarrowmanCalculator"); - writeln(""); - indent++; + + // Write out custom expressions + if (!simulation.getCustomExpressions().isEmpty()){ + writeln(""); indent++; + for (CustomExpression expression : simulation.getCustomExpressions()){ + writeln(""); indent++; + writeElement("name", expression.getName()); + writeElement("symbol", expression.getSymbol()); + writeElement("unit", expression.getUnit()); + writeElement("expressionstring", expression.getExpressionString()); + indent--; writeln(""); + } + indent--; writeln(""); + } + + writeln(""); indent++; writeElement("configid", cond.getMotorConfigurationID()); writeElement("launchrodlength", cond.getLaunchRodLength()); @@ -359,7 +375,6 @@ public class OpenRocketSaver extends RocketSaver { writeElement("listener", escapeXML(s)); } - // Write basic simulation data FlightData data = simulation.getSimulatedData(); diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java index 6a112f55..a56f0d78 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java @@ -67,6 +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.FlightData; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; @@ -1201,6 +1202,8 @@ class SimulationsHandler extends AbstractElementHandler { } class SingleSimulationHandler extends AbstractElementHandler { + private static final LogHelper log = Application.getLogger(); + private final DocumentLoadingContext context; private final OpenRocketDocument doc; @@ -1209,7 +1212,9 @@ class SingleSimulationHandler extends AbstractElementHandler { private SimulationConditionsHandler conditionHandler; private FlightDataHandler dataHandler; - + private CustomExpressionsHandler customExpressionsHandler; + + private ArrayList customExpressions = new ArrayList(); private final List listeners = new ArrayList(); public SingleSimulationHandler(OpenRocketDocument doc, DocumentLoadingContext context) { @@ -1217,7 +1222,9 @@ class SingleSimulationHandler extends AbstractElementHandler { this.context = context; } - + public void setCustomExpressions(ArrayList expressions){ + this.customExpressions = expressions; + } @Override public ElementHandler openElement(String element, HashMap attributes, @@ -1226,6 +1233,9 @@ 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; @@ -1288,13 +1298,70 @@ class SingleSimulationHandler extends AbstractElementHandler { Simulation simulation = new Simulation(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 customExpressions = new ArrayList(); + + public CustomExpressionsHandler(SingleSimulationHandler simHandler, DocumentLoadingContext context) { + this.context = context; + this.simHandler = simHandler; + } + + @Override + public ElementHandler openElement(String element, + HashMap attributes, WarningSet warnings) + throws SAXException { + + if (element.equals("expression")){ + currentExpression = new CustomExpression(); + } + + return this; + } + @Override + public void closeElement(String element, HashMap 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 attributes, + String content, WarningSet warnings) { + simHandler.setCustomExpressions(customExpressions); + } +} + class SimulationConditionsHandler extends AbstractElementHandler { private final DocumentLoadingContext context; private SimulationOptions conditions; @@ -1605,7 +1672,8 @@ class FlightDataBranchHandler extends AbstractElementHandler { String[] split = typeList.split(","); types = new FlightDataType[split.length]; for (int i = 0; i < split.length; i++) { - types[i] = FlightDataType.getType(split[i], UnitGroup.UNITS_NONE); + types[i] = FlightDataType.getType(split[i], "None ("+split[i]+")", UnitGroup.UNITS_NONE); + // TODO: HIGH: Deal with symbols } // TODO: LOW: May throw an IllegalArgumentException diff --git a/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java b/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java new file mode 100644 index 00000000..e0bd2360 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java @@ -0,0 +1,195 @@ +package net.sf.openrocket.gui.customexpression; + +import java.awt.Color; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collections; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.components.DescriptionArea; +import net.sf.openrocket.gui.components.UnitSelector; +import net.sf.openrocket.gui.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.startup.Application; + +public class CustomExpressionPanel extends JPanel { + + private static final LogHelper log = Application.getLogger(); + private static final Translator trans = Application.getTranslator(); + + private JPanel expressionSelectorPanel; + private Simulation simulation; + + public CustomExpressionPanel(final Simulation simulation) { + super(new MigLayout("fill")); + this.simulation = simulation; + + 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"); + + 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"); + + //// New expression + JButton button = new JButton(trans.get("customExpressionPanel.but.NewExpression")); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // Open window to configure expression + log.debug("Opening window to configure new expression"); + Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this); + new ExpressionBuilderDialog(parent, simulation).setVisible(true); + updateExpressions(); + } + }); + + this.add(button, "left"); + + updateExpressions(); + } + + /* + * Update the expressionSelectorPanel + */ + private void updateExpressions(){ + + expressionSelectorPanel.removeAll(); + for (CustomExpression expression : simulation.getCustomExpressions()){ + SingleExpression se = new SingleExpression(expression); + 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 + + + } + + private void deleteExpression(CustomExpression expression){ + simulation.getCustomExpressions().remove(expression); + } + + /** + * Moves an expression up or down in the expression list + * @param expression + * @param move integer - +1 to move down, -1 to move up + */ + private void moveExpression(CustomExpression expression, int move){ + ArrayList expressions = simulation.getCustomExpressions(); + int i = expressions.indexOf(expression); + if (i+move == expressions.size() || i+move < 0) + return; + else + Collections.swap(expressions, i, i+move); + } + + + /* + * A JPanel which configures a single expression + */ + private class SingleExpression extends JPanel { + + // Convenience method to make the labels consistent + private JLabel setLabelStyle(JLabel l){ + l.setBackground(Color.WHITE); + l.setOpaque(true); + l.setBorder(BorderFactory.createRaisedBevelBorder() ); + l.setText(" " + l.getText() + " "); + return l; + } + + private SingleExpression(final CustomExpression expression) { + super(new MigLayout("ins 0")); + // name: aName symbol: a Unit: m/s + //super(new MigLayout("","[::100][:200:400][::100][:100:200][::100][:100:200]","")); + + JLabel nameLabel = new JLabel( trans.get("customExpression.Name")+ " :"); + JLabel name = new JLabel ( expression.getName() ); + name = setLabelStyle(name); + JLabel symbolLabel = new JLabel( trans.get("customExpression.Symbol")+ " :" ); + JLabel symbol = new JLabel ( expression.getSymbol()); + symbol = setLabelStyle(symbol); + symbol.setBackground(Color.WHITE); + + JLabel unitLabel = new JLabel( trans.get("customExpression.Units")+ " :"); + UnitSelector unitSelector = new UnitSelector(expression.getType().getUnitGroup()); + + JButton editButton = new JButton(Icons.EDIT); + editButton.setToolTipText(trans.get("customExpression.Units.but.ttip.Edit")); + editButton.setBorderPainted(false); + editButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e){ + Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this); + expression.editExpression(parent); + updateExpressions(); + } + }); + + JButton upButton = new JButton(Icons.UP); + upButton.setToolTipText(trans.get("customExpression.Units.but.ttip.MoveUp")); + upButton.setBorderPainted(false); + upButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + moveExpression(expression, -1); + updateExpressions(); + } + }); + + JButton downButton = new JButton(Icons.DOWN); + downButton.setToolTipText(trans.get("customExpression.Units.but.ttip.MoveDown")); + downButton.setBorderPainted(false); + downButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + moveExpression(expression, 1); + updateExpressions(); + } + }); + + + JButton deleteButton = new JButton(Icons.DELETE); + //// Remove this expression + deleteButton.setToolTipText(trans.get("customExpression.Units.but.ttip.Remove")); + deleteButton.setBorderPainted(false); + deleteButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + deleteExpression(expression); + updateExpressions(); + } + }); + + this.add(nameLabel); + this.add(name, "width 200:200:400, growx"); + this.add(new JPanel()); + this.add(symbolLabel); + this.add(symbol, "width :50:200"); + this.add(new JPanel()); + this.add(unitLabel); + this.add(unitSelector, "width :50:100"); + this.add(new JPanel(), "growx"); + this.add(upButton, "right"); + this.add(downButton, "right"); + this.add(editButton, "right"); + this.add(deleteButton, "right"); + } + } +} diff --git a/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java b/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java new file mode 100644 index 00000000..0c25cbf8 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java @@ -0,0 +1,266 @@ +package net.sf.openrocket.gui.customexpression; + +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; + +import net.miginfocom.swing.MigLayout; +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.startup.Application; + +/** + * Dialog box for making a custom expression + * @author Richard Graham + * + */ + +public class ExpressionBuilderDialog extends JDialog { + + private static final Translator trans = Application.getTranslator(); + private static final LogHelper log = Application.getLogger(); + + private static final ImageIcon GreenIcon = Icons.loadImageIcon("pix/spheres/green-16x16.png", "OK"); + private static final ImageIcon RedIcon = Icons.loadImageIcon("pix/spheres/red-16x16.png", "Bad"); + + private CustomExpression expression; + private CustomExpression previousExpressionCopy; + + private final Window parentWindow; + private final Simulation simulation; + + // Define these check indicators to show if fields are OK + private final JLabel nameCheck = new JLabel(RedIcon); + private final JLabel expressionCheck = new JLabel(RedIcon); + private final JLabel unitCheck = new JLabel(RedIcon); + private final JButton okButton = new JButton(trans.get("dlg.but.ok")); + private final JTextField expressionField = new JTextField(20); + + public ExpressionBuilderDialog(Window parent, Simulation simulation){ + this(parent, simulation, new CustomExpression(simulation)); + } + + public ExpressionBuilderDialog(Window parent, final Simulation simulation, final CustomExpression previousExpression){ + + super(parent, trans.get("ExpressionBuilderDialog.title"), JDialog.ModalityType.DOCUMENT_MODAL); + + this.parentWindow = parent; + this.simulation = simulation; + this.previousExpressionCopy = (CustomExpression) previousExpression.clone(); + this.expression = previousExpression; + + //// Name box -- Check input when focus changes and transfer focus to next box on enter key + JLabel nameLabel = new JLabel(trans.get("customExpression.Name")); + final JTextField nameField = new JTextField(20); + nameField.setText(expression.getName()); + nameField.setFocusTraversalKeysEnabled(true); + nameField.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { } + + @Override + public void focusLost(FocusEvent e) { + expression.setName(nameField.getText()); + ExpressionBuilderDialog.this.updateOK(); + } + }); + nameField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + nameField.transferFocus(); + } + }); + + //// Expression box -- for this one we check after each keypress using a keyListener. Enter transfers to next field + JLabel expressionLabel = new JLabel(trans.get("customExpression.Expression")); + expressionField.setText(expression.getExpressionString()); + expressionField.addKeyListener(new KeyListener() { + @Override + public void keyReleased(KeyEvent arg0) { + expression.setExpression( expressionField.getText() ); + ExpressionBuilderDialog.this.updateOK(); + } + + @Override + public void keyPressed(KeyEvent e) {} + + @Override + public void keyTyped(KeyEvent e) {} + }); + expressionField.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent e) { + expressionField.transferFocus(); + } + }); + + //// Units box -- with action listeners checking input after change in focus or enter press + JLabel unitLabel = new JLabel(trans.get("customExpression.Units")); + final JTextField unitField = new JTextField(5); + unitField.setText(expression.getUnit()); + unitField.addFocusListener(new FocusListener(){ + @Override + public void focusLost(FocusEvent arg0) { + expression.setUnit(unitField.getText()) ; + ExpressionBuilderDialog.this.updateOK(); + } + @Override + public void focusGained(FocusEvent arg0) {} + }); + unitField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + unitField.transferFocus(); + } + }); + + //// Symbol box + JLabel symbolLabel = new JLabel(trans.get("customExpression.Symbol")); + final JTextField symbolField = new JTextField(5); + symbolField.setText(expression.getSymbol()); + symbolField.addFocusListener(new FocusListener(){ + @Override + public void focusLost(FocusEvent arg0) { + expression.setSymbol(symbolField.getText()) ; + ExpressionBuilderDialog.this.updateOK(); + } + @Override + public void focusGained(FocusEvent arg0) {} + }); + symbolField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + symbolField.transferFocus(); + } + }); + + + //// Insert variable button + final JButton insertVariableButton = new JButton(trans.get("ExpressionBuilderDialog.InsertVariable")); + insertVariableButton.addActionListener(new ActionListener() { + @Override + 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); + } + }); + + //// Insert operator button + final JButton insertOperatorButton = new JButton(trans.get("ExpressionBuilderDialog.InsertOperator")); + insertOperatorButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.debug("Opening insert operator window"); + Window parentWindow = SwingUtilities.getWindowAncestor(ExpressionBuilderDialog.this); + new OperatorSelector(parentWindow, ExpressionBuilderDialog.this).setVisible(true); + } + }); + + + //// OK Button + okButton.setEnabled(false); + okButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + expression.addToSimulation(); + ExpressionBuilderDialog.this.dispose(); + } + }); + + //// Cancel button + final JButton cancelButton = new JButton(trans.get("dlg.but.cancel")); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + expression.overwrite(previousExpressionCopy); + ExpressionBuilderDialog.this.dispose(); + } + }); + + //// Set to tips + nameCheck.setToolTipText(trans.get("ExpressionBuilderDialog.led.ttip.Name")); + unitCheck.setToolTipText(trans.get("ExpressionBuilderDialog.led.ttip.Symbol")); + expressionCheck.setToolTipText(trans.get("ExpressionBuilderDialog.led.ttip.Expression")); + + //// Do the layout + JPanel mainPanel = new JPanel(new MigLayout()); + mainPanel.add(nameLabel); + mainPanel.add(nameField); + mainPanel.add(nameCheck, "wrap, center"); + mainPanel.add(symbolLabel); + mainPanel.add(symbolField, "split 4, growx"); + mainPanel.add(new JPanel()); + mainPanel.add(unitLabel, "right"); + mainPanel.add(unitField, "right, growx"); + mainPanel.add(unitCheck, "wrap, center"); + mainPanel.add(expressionLabel); + mainPanel.add(expressionField); + mainPanel.add(expressionCheck, "wrap, center"); + mainPanel.add(insertOperatorButton, "span 2, right, split 2"); + mainPanel.add(insertVariableButton, "right, wrap"); + mainPanel.add(cancelButton, "span 2, right, width :50:100"); + mainPanel.add(okButton, "right, width :50:100, wrap"); + + this.add(mainPanel); + this.validate(); + this.pack(); + this.setLocationByPlatform(true); + this.updateOK(); + + } + + /** + * Enable OK button only if all the fields are ok + * @param okButton + */ + protected void updateOK() { + + boolean nameOK = expression.checkName(); + boolean unitOK = expression.checkUnit(); + boolean symbolOK = expression.checkSymbol(); + boolean expressionOK = expression.checkExpression(); + + if (nameOK) { nameCheck.setIcon(GreenIcon); } else { nameCheck.setIcon(RedIcon); } + if (unitOK && symbolOK) { unitCheck.setIcon(GreenIcon); } else { unitCheck.setIcon(RedIcon); } + if (expressionOK) { expressionCheck.setIcon(GreenIcon); } else { expressionCheck.setIcon(RedIcon); } + + okButton.setEnabled( nameOK && unitOK && symbolOK && expressionOK ); + } + + /** + * Inserts a string into the expression box at the position of the cursor. + * String will be padded with spaces either side + * Expression box will be focused after this is called. + * For strings containing an ( , cursor will be moved to the point after that, otherwise, cursor will move to the end of the inserted string. + * @param str + */ + public void pasteIntoExpression(String str) { + int pos = expressionField.getCaretPosition(); + String current = expressionField.getText(); + expressionField.setText(current.subSequence(0, pos) + " " + str + " " + current.subSequence(pos, current.length())); + expressionField.requestFocus(); + int bracketPos = str.indexOf("("); + if (bracketPos != -1){ + expressionField.setCaretPosition(pos+2+bracketPos); + } + else { + expressionField.setCaretPosition(pos+2+str.length()); + } + } +} diff --git a/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java b/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java new file mode 100644 index 00000000..bc1f8066 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java @@ -0,0 +1,90 @@ +package net.sf.openrocket.gui.customexpression; + +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.startup.Application; + +public class OperatorSelector extends JDialog { + + private static final Translator trans = Application.getTranslator(); + private static final LogHelper log = Application.getLogger(); + + private final Window parentWindow; + + public OperatorSelector(Window parent, final ExpressionBuilderDialog parentBuilder){ + + super(parent, trans.get("CustomOperatorSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL); + + this.parentWindow = parent; + + 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); + + 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)); + + 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); + } + } + }); + + mainPanel.add(scrollPane, "wrap"); + + //// Cancel button + final JButton cancelButton = new JButton(trans.get("dlg.but.cancel")); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + OperatorSelector.this.dispose(); + } + }); + 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.getOperatorAt(row); + parentBuilder.pasteIntoExpression(str); + OperatorSelector.this.dispose(); + } + }); + insertButton.setEnabled(false); // disabled by default, only enable when a variable selected + mainPanel.add(insertButton, "right, width :100:200, wrap"); + + this.add(mainPanel); + this.validate(); + this.pack(); + this.setLocationByPlatform(true); + } +} diff --git a/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java b/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java new file mode 100644 index 00000000..76a1a8f2 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java @@ -0,0 +1,52 @@ +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.startup.Application; + +public class OperatorTableModel extends AbstractTableModel { + + private static final Translator trans = Application.getTranslator(); + + 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(); + + public OperatorTableModel(){ + + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public int getRowCount() { + return CustomExpression.AVAILABLE_OPERATORS.size(); + } + + @Override + public Object getValueAt(int row, int col) { + if (col == 0){ + return operators[row].toString(); + } + else if (col == 1){ + return descriptions[row].toString(); + } + return null; + } + + @Override + public String getColumnName(int col) { + return columnNames[col]; + } + + public String getOperatorAt(int row) { + return operators[row].toString(); + } + +} diff --git a/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java b/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java new file mode 100644 index 00000000..d432a18c --- /dev/null +++ b/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java @@ -0,0 +1,100 @@ +package net.sf.openrocket.gui.customexpression; + +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.startup.Application; + +/** + * Dialog to select from available custom variables + * @author Richard Graham + * + */ + +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){ + + super(parent, trans.get("CustomVariableSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL); + + this.parentWindow = parent; + this.simulation = simulation; + + 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); + + 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)); + + 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); + } + } + }); + + mainPanel.add(scrollPane, "wrap"); + + //// Cancel button + final JButton cancelButton = new JButton(trans.get("dlg.but.cancel")); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + VariableSelector.this.dispose(); + } + }); + 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(); + } + }); + insertButton.setEnabled(false); // disabled by default, only enable when a variable selected + mainPanel.add(insertButton, "right, width :100:200, wrap"); + + this.add(mainPanel); + this.validate(); + this.pack(); + this.setLocationByPlatform(true); + } +} diff --git a/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java b/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java new file mode 100644 index 00000000..b81a6b05 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java @@ -0,0 +1,77 @@ +/** + * + */ +package net.sf.openrocket.gui.customexpression; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import javax.swing.table.AbstractTableModel; + +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.simulation.CustomExpression; +import net.sf.openrocket.simulation.FlightDataType; +import net.sf.openrocket.startup.Application; + +/** + * @author Richard Graham + * + */ +public class VariableTableModel extends AbstractTableModel { + + private static final Translator trans = Application.getTranslator(); + + private ArrayList types = new ArrayList(); + private static final String[] columnNames = {trans.get("customExpression.Name"), trans.get("customExpression.Symbol"), trans.get("customExpression.Units")}; + + /* + * Table model will be constructed with all the built in variables and any custom variables defined + */ + public VariableTableModel(Simulation sim){ + + Collections.addAll(types, FlightDataType.ALL_TYPES); + + for (CustomExpression expression : sim.getCustomExpressions()){ + types.add(expression.getType()); + } + + } + + @Override + public int getColumnCount() { + return 3; + } + + @Override + public int getRowCount() { + return types.size(); + } + + @Override + public Object getValueAt(int row, int col) { + if (col == 0) + return types.get(row).getName(); + else if (col == 1) + return types.get(row).getSymbol(); + else if (col == 2) + return types.get(row).getUnitGroup().getDefaultUnit().toString(); + + return null; + } + + @Override + public String getColumnName(int col) { + return columnNames[col]; + } + + public String getSymbolAt(int row) { + if (row < 0 || row > types.size()){ + return ""; + } + else { + return types.get(row).getSymbol(); + } + } +} diff --git a/core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java b/core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java index cb9e0d34..daba47e7 100644 --- a/core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java +++ b/core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java @@ -41,6 +41,7 @@ import net.sf.openrocket.gui.components.DescriptionArea; import net.sf.openrocket.gui.components.SimulationExportPanel; import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.gui.plot.Axis; +import net.sf.openrocket.gui.customexpression.CustomExpressionPanel; import net.sf.openrocket.gui.plot.PlotConfiguration; import net.sf.openrocket.gui.plot.SimulationPlotPanel; import net.sf.openrocket.gui.util.GUIUtil; @@ -77,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 = 2; + public static final int PLOT = 3; private final Window parentWindow; @@ -138,6 +139,8 @@ 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 @@ -147,7 +150,7 @@ public class SimulationEditDialog extends JDialog { if (tab == EDIT) { tabbedPane.setSelectedIndex(0); } else if (tab == PLOT) { - tabbedPane.setSelectedIndex(2); + tabbedPane.setSelectedIndex(3); } else { FlightData data = s.getSimulatedData(); if (data == null || data.getBranchCount() == 0) @@ -835,7 +838,9 @@ public class SimulationEditDialog extends JDialog { } - + private JPanel customExpressionsTab() { + return new CustomExpressionPanel(simulation); + } /** diff --git a/core/src/net/sf/openrocket/gui/util/Icons.java b/core/src/net/sf/openrocket/gui/util/Icons.java index 8f304b63..b202ff4e 100644 --- a/core/src/net/sf/openrocket/gui/util/Icons.java +++ b/core/src/net/sf/openrocket/gui/util/Icons.java @@ -73,6 +73,10 @@ public class Icons { public static final Icon PREFERENCES = loadImageIcon("pix/icons/preferences.png", "Preferences"); public static final Icon DELETE = loadImageIcon("pix/icons/delete.png", "Delete"); + public static final Icon EDIT = loadImageIcon("pix/icons/pencil.png", "Edit"); + public static final Icon UP = loadImageIcon("pix/icons/up.png", "Up"); + public static final Icon DOWN = loadImageIcon("pix/icons/down.png", "Down"); + static { log.debug("Icons loaded"); diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 305516ad..b0139fbb 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -1,5 +1,6 @@ package net.sf.openrocket.simulation; +import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -88,6 +89,12 @@ public class BasicEventSimulationEngine implements SimulationEngine { } SimulationListenerHelper.firePostStep(status); + // Calculate values for custom expressions + FlightDataBranch data = status.getFlightData(); + ArrayList allExpressions = status.getSimulationConditions().getSimulation().getCustomExpressions(); + for (CustomExpression expression : allExpressions ) { + data.setValue(expression.getType(), expression.evaluate(status)); + } // Check for NaN values in the simulation status checkNaN(); diff --git a/core/src/net/sf/openrocket/simulation/CustomExpression.java b/core/src/net/sf/openrocket/simulation/CustomExpression.java new file mode 100644 index 00000000..d060c07c --- /dev/null +++ b/core/src/net/sf/openrocket/simulation/CustomExpression.java @@ -0,0 +1,317 @@ +package net.sf.openrocket.simulation; + +import java.awt.Window; +import java.util.SortedMap; +import java.util.TreeMap; + +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.customexpression.ExpressionBuilderDialog; +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 String name, symbol, unit, expression; + private ExpressionBuilder builder; + private static Simulation sim = null; + + // A map of available operator strings (keys) and description of function (value) + public static final SortedMap AVAILABLE_OPERATORS = new TreeMap() {{ + put("+" , "Addition"); + put("-" , "Subtraction"); + put("*" , "Multiplication"); + put("/" , "Divison"); + put("%" , "Modulo"); + put("^" , "Exponentiation"); + put("abs()" , "Absolute value"); + put("ceil()" , "Ceiling (next integer value"); + put("floor()" , "Floor (previous integer value"); + put("sqrt()" , "Square root"); + put("cbrt()" , "Cubic root"); + put("exp()" , "Euler\'s number raised to the value (e^x)"); + put("log()" , "Natural logarithm"); + put("sin()" , "Sine"); + put("cos()" , "Cosine"); + put("tan()" , "Tangent"); + put("asin()" , "Arc sine"); + put("acos()" , "Arc cosine"); + put("atan()" , "Arc tangent"); + put("sinh()" , "Hyerbolic sine"); + put("cosh()" , "Hyperbolic cosine"); + put("tanh()" , "Hyperbolic tangent"); + }}; + + + 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); + } + + /* + * Opens an ExpressionBuilderDialog for this expression + */ + public void editExpression(Window parent){ + log.debug("Opening window to edit an existing custom expression"); + new ExpressionBuilderDialog(parent, sim, this).setVisible(true); + } + + /* + * Use this to update the simulation this is associated with + */ + public void setSimulation(Simulation sim){ + CustomExpression.sim = 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().getBranch(0) == null) { + 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 getAllNames(){ + ArrayList names = new ArrayList(); + 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 getAllSymbols(){ + ArrayList symbols = new ArrayList(); + 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 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; + + ArrayList names = getAllNames().clone(); + if (names.contains(name.trim())){ + int index = names.indexOf(name.trim()); + log.user("Symbol "+symbol+" 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); + return FlightDataType.getType(name, symbol, ug); + } + + /* + * Add this expression to the simulation if not already added + */ + public void addToSimulation(){ + if (! sim.getCustomExpressions().contains(this)) + 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); + } + } + + @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 + * @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())); + } + } + +} diff --git a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java index f3eb3d9c..475a068e 100644 --- a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java +++ b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java @@ -68,7 +68,16 @@ public class FlightDataBranch implements Monitorable { } } - + /** + * Makes an 'empty' flight data branch which has no data but all built in data types are defined. + */ + public FlightDataBranch() { + branchName = "Empty branch"; + for (FlightDataType type : FlightDataType.ALL_TYPES){ + this.setValue(type, Double.NaN); + } + this.immute(); + } /** * Adds a new point into the data branch. The value for all types is set to NaN by default. diff --git a/core/src/net/sf/openrocket/simulation/FlightDataType.java b/core/src/net/sf/openrocket/simulation/FlightDataType.java index 0e8b41a4..9a7df764 100644 --- a/core/src/net/sf/openrocket/simulation/FlightDataType.java +++ b/core/src/net/sf/openrocket/simulation/FlightDataType.java @@ -33,152 +33,206 @@ public class FlightDataType implements Comparable { //// Time - public static final FlightDataType TYPE_TIME = newType(trans.get("FlightDataType.TYPE_TIME"), 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(trans.get("FlightDataType.TYPE_ALTITUDE"), UnitGroup.UNITS_DISTANCE, 10); + public static final FlightDataType TYPE_ALTITUDE = newType(trans.get("FlightDataType.TYPE_ALTITUDE"), "a", UnitGroup.UNITS_DISTANCE, 10); //// Vertical velocity - public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), 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(trans.get("FlightDataType.TYPE_ACCELERATION_Z"), 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(trans.get("FlightDataType.TYPE_VELOCITY_TOTAL"), 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(trans.get("FlightDataType.TYPE_ACCELERATION_TOTAL"), 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(trans.get("FlightDataType.TYPE_POSITION_X"), 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(trans.get("FlightDataType.TYPE_POSITION_Y"), 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(trans.get("FlightDataType.TYPE_POSITION_XY"), 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(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), UnitGroup.UNITS_ANGLE, 33); + public static final FlightDataType TYPE_POSITION_DIRECTION = newType(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), "θl", UnitGroup.UNITS_ANGLE, 33); //// Lateral velocity - public static final FlightDataType TYPE_VELOCITY_XY = newType(trans.get("FlightDataType.TYPE_VELOCITY_XY"), 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(trans.get("FlightDataType.TYPE_ACCELERATION_XY"), 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(trans.get("FlightDataType.TYPE_LATITUDE"), UnitGroup.UNITS_ANGLE, 36); + public static final FlightDataType TYPE_LATITUDE = newType(trans.get("FlightDataType.TYPE_LATITUDE"), "φ", UnitGroup.UNITS_ANGLE, 36); //// Longitude - public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), UnitGroup.UNITS_ANGLE, 37); + public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "λ", UnitGroup.UNITS_ANGLE, 37); //// Angular motion //// Angle of attack - public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), UnitGroup.UNITS_ANGLE, 40); + public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), "α", UnitGroup.UNITS_ANGLE, 40); //// Roll rate - public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), UnitGroup.UNITS_ROLL, 41); + public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), "dΦ", UnitGroup.UNITS_ROLL, 41); //// Pitch rate - public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), UnitGroup.UNITS_ROLL, 42); + public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), "dθ", UnitGroup.UNITS_ROLL, 42); //// Yaw rate - public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), UnitGroup.UNITS_ROLL, 43); + public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), "dΨ", UnitGroup.UNITS_ROLL, 43); //// Stability information //// Mass - public static final FlightDataType TYPE_MASS = newType(trans.get("FlightDataType.TYPE_MASS"), 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(trans.get("FlightDataType.TYPE_LONGITUDINAL_INERTIA"), 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(trans.get("FlightDataType.TYPE_ROTATIONAL_INERTIA"), 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(trans.get("FlightDataType.TYPE_CP_LOCATION"), 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(trans.get("FlightDataType.TYPE_CG_LOCATION"), 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(trans.get("FlightDataType.TYPE_STABILITY"), 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(trans.get("FlightDataType.TYPE_MACH_NUMBER"), 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(trans.get("FlightDataType.TYPE_REYNOLDS_NUMBER"), 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(trans.get("FlightDataType.TYPE_THRUST_FORCE"), 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(trans.get("FlightDataType.TYPE_DRAG_FORCE"), 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(trans.get("FlightDataType.TYPE_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_AXIAL_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_FRICTION_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_PRESSURE_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_BASE_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_NORMAL_FORCE_COEFF"), 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(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 91); + public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), "Cθ", UnitGroup.UNITS_COEFFICIENT, 91); //// Yaw moment coefficient - public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 92); + public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), "CτΨ", UnitGroup.UNITS_COEFFICIENT, 92); //// Side force coefficient - public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), UnitGroup.UNITS_COEFFICIENT, 93); + public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), "Cτs", UnitGroup.UNITS_COEFFICIENT, 93); //// Roll moment coefficient - public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 94); + public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), "CτΦ", UnitGroup.UNITS_COEFFICIENT, 94); //// Roll forcing coefficient - public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), UnitGroup.UNITS_COEFFICIENT, 95); + public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), "CfΦ", UnitGroup.UNITS_COEFFICIENT, 95); //// Roll damping coefficient - public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), UnitGroup.UNITS_COEFFICIENT, 96); + public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), "CζΦ", UnitGroup.UNITS_COEFFICIENT, 96); //// Pitch damping coefficient - public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 97); + public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), "Cζθ", UnitGroup.UNITS_COEFFICIENT, 97); //// Yaw damping coefficient - public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 98); + public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), "CζΨ", UnitGroup.UNITS_COEFFICIENT, 98); //// Coriolis acceleration - public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType(trans.get("FlightDataType.TYPE_CORIOLIS_ACCELERATION"), 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(trans.get("FlightDataType.TYPE_REFERENCE_LENGTH"), 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(trans.get("FlightDataType.TYPE_REFERENCE_AREA"), 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(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), UnitGroup.UNITS_ANGLE, 106); + public static final FlightDataType TYPE_ORIENTATION_THETA = newType(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), "Θ", UnitGroup.UNITS_ANGLE, 106); //// Lateral orientation (azimuth) - public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), UnitGroup.UNITS_ANGLE, 107); + public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), "Φ", UnitGroup.UNITS_ANGLE, 107); //// Atmospheric conditions //// Wind velocity - public static final FlightDataType TYPE_WIND_VELOCITY = newType(trans.get("FlightDataType.TYPE_WIND_VELOCITY"), 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(trans.get("FlightDataType.TYPE_AIR_TEMPERATURE"), 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(trans.get("FlightDataType.TYPE_AIR_PRESSURE"), 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(trans.get("FlightDataType.TYPE_SPEED_OF_SOUND"), 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(trans.get("FlightDataType.TYPE_TIME_STEP"), 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(trans.get("FlightDataType.TYPE_COMPUTATION_TIME"), 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 = { + TYPE_ALTITUDE , + TYPE_VELOCITY_Z , + TYPE_ACCELERATION_Z, + TYPE_VELOCITY_TOTAL, + TYPE_ACCELERATION_TOTAL, + TYPE_POSITION_X, + TYPE_POSITION_Y, + TYPE_POSITION_XY, + TYPE_POSITION_DIRECTION, + TYPE_VELOCITY_XY, + TYPE_ACCELERATION_XY, + TYPE_LATITUDE, + TYPE_LONGITUDE, + TYPE_AOA, + TYPE_ROLL_RATE, + TYPE_PITCH_RATE, + TYPE_YAW_RATE, + TYPE_MASS, + TYPE_LONGITUDINAL_INERTIA, + TYPE_ROTATIONAL_INERTIA, + TYPE_CP_LOCATION, + TYPE_CG_LOCATION, + TYPE_STABILITY, + TYPE_MACH_NUMBER, + TYPE_REYNOLDS_NUMBER, + TYPE_THRUST_FORCE, + TYPE_DRAG_FORCE, + TYPE_DRAG_COEFF, + TYPE_AXIAL_DRAG_COEFF, + TYPE_FRICTION_DRAG_COEFF, + TYPE_PRESSURE_DRAG_COEFF, + TYPE_BASE_DRAG_COEFF, + TYPE_NORMAL_FORCE_COEFF, + TYPE_PITCH_MOMENT_COEFF, + TYPE_YAW_MOMENT_COEFF, + TYPE_SIDE_FORCE_COEFF, + TYPE_ROLL_MOMENT_COEFF, + TYPE_ROLL_FORCING_COEFF, + TYPE_ROLL_DAMPING_COEFF, + TYPE_PITCH_DAMPING_MOMENT_COEFF, + TYPE_YAW_DAMPING_MOMENT_COEFF, + TYPE_CORIOLIS_ACCELERATION, + TYPE_REFERENCE_LENGTH, + TYPE_REFERENCE_AREA, + TYPE_ORIENTATION_THETA, + TYPE_ORIENTATION_PHI, + TYPE_WIND_VELOCITY, + TYPE_AIR_TEMPERATURE, + TYPE_AIR_PRESSURE, + TYPE_SPEED_OF_SOUND, + TYPE_TIME_STEP, + TYPE_COMPUTATION_TIME + }; /** * Return a {@link FlightDataType} based on a string description. This returns known data types @@ -188,37 +242,39 @@ public class FlightDataType implements Comparable { * @param u the unit group the new type should belong to if a new group is created. * @return a data type. */ - public static synchronized FlightDataType getType(String s, UnitGroup u) { + public static synchronized FlightDataType getType(String s, String symbol, UnitGroup u) { FlightDataType type = EXISTING_TYPES.get(s.toLowerCase(Locale.ENGLISH)); if (type != null) { return type; } - type = newType(s, u, DEFAULT_PRIORITY); + type = newType(s, symbol, u, DEFAULT_PRIORITY); return type; } /** * Used while initializing the class. */ - private static synchronized FlightDataType newType(String s, UnitGroup u, int priority) { - FlightDataType type = new FlightDataType(s, u, priority); + 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); return type; } private final String name; + private final String symbol; private final UnitGroup units; private final int priority; private final int hashCode; - private FlightDataType(String typeName, UnitGroup units, int priority) { + private FlightDataType(String typeName, String symbol, UnitGroup units, int priority) { if (typeName == null) throw new IllegalArgumentException("typeName is null"); if (units == null) throw new IllegalArgumentException("units is null"); this.name = typeName; + this.symbol = symbol; this.units = units; this.priority = priority; this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode(); @@ -231,6 +287,10 @@ public class FlightDataType implements Comparable { return name; } + public String getSymbol(){ + return symbol; + } + public UnitGroup getUnitGroup() { return units; } diff --git a/core/src/net/sf/openrocket/simulation/SimulationConditions.java b/core/src/net/sf/openrocket/simulation/SimulationConditions.java index f2412717..ffd9e003 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationConditions.java +++ b/core/src/net/sf/openrocket/simulation/SimulationConditions.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.document.Simulation; import net.sf.openrocket.masscalc.MassCalculator; import net.sf.openrocket.models.atmosphere.AtmosphericModel; import net.sf.openrocket.models.gravity.GravityModel; @@ -27,6 +28,7 @@ public class SimulationConditions implements Monitorable, Cloneable { private Rocket rocket; private String motorID = null; + private Simulation simulation; // The parent simulation private double launchRodLength = 1; @@ -262,9 +264,14 @@ public class SimulationConditions implements Monitorable, Cloneable { this.modID++; } - - + public void setSimulation(Simulation sim) { + this.simulation = sim; + } + public Simulation getSimulation(){ + return this.simulation; + } + // TODO: HIGH: Make cleaner public List getSimulationListenerList() { return simulationListeners; diff --git a/core/src/net/sf/openrocket/simulation/SimulationOptions.java b/core/src/net/sf/openrocket/simulation/SimulationOptions.java index 7734ec5d..a1b6bae9 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationOptions.java +++ b/core/src/net/sf/openrocket/simulation/SimulationOptions.java @@ -5,8 +5,10 @@ import java.util.EventListener; import java.util.EventObject; import java.util.List; import java.util.Random; +import java.util.Set; import net.sf.openrocket.aerodynamics.BarrowmanCalculator; +import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.masscalc.BasicMassCalculator; import net.sf.openrocket.models.atmosphere.AtmosphericModel; import net.sf.openrocket.models.atmosphere.ExtendedISAModel; @@ -14,6 +16,7 @@ import net.sf.openrocket.models.gravity.GravityModel; import net.sf.openrocket.models.gravity.WGSGravityModel; import net.sf.openrocket.models.wind.PinkNoiseWindModel; import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.ChangeSource; import net.sf.openrocket.util.GeodeticComputationStrategy; @@ -31,6 +34,8 @@ import net.sf.openrocket.util.WorldCoordinate; */ public class SimulationOptions implements ChangeSource, Cloneable { + private static final LogHelper log = Application.getLogger(); + public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3; /** @@ -93,7 +98,6 @@ public class SimulationOptions implements ChangeSource, Cloneable { this.rocket = rocket; } - public Rocket getRocket() { return rocket; } diff --git a/core/src/net/sf/openrocket/simulation/listeners/example/RollControlListener.java b/core/src/net/sf/openrocket/simulation/listeners/example/RollControlListener.java index 80993218..4c99375b 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/example/RollControlListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/example/RollControlListener.java @@ -22,8 +22,7 @@ public class RollControlListener extends AbstractSimulationListener { private static final String CONTROL_FIN_NAME = "CONTROL"; // Define custom flight data type - private static final FlightDataType FIN_CANT_TYPE = FlightDataType.getType("Control fin cant", - UnitGroup.UNITS_ANGLE); + private static final FlightDataType FIN_CANT_TYPE = FlightDataType.getType("Control fin cant", "αfc", UnitGroup.UNITS_ANGLE); // Simulation time at which PID controller is activated private static final double START_TIME = 0.5; diff --git a/core/src/net/sf/openrocket/unit/FixedUnitGroup.java b/core/src/net/sf/openrocket/unit/FixedUnitGroup.java new file mode 100644 index 00000000..a0984961 --- /dev/null +++ b/core/src/net/sf/openrocket/unit/FixedUnitGroup.java @@ -0,0 +1,31 @@ +package net.sf.openrocket.unit; + +/* + * This class provides a 'dumb' version of UnitGroup + * It allows any arbitrary unit to be created. It doesn't store any value and can't be converted into anything else. + * This is useful for custom expression units. + * + * @author Richard Graham + */ + +public class FixedUnitGroup extends UnitGroup { + + String unitString; + + public FixedUnitGroup( String unitString ){ + this.unitString = unitString; + } + + public int getUnitCount(){ + return 1; + } + + public Unit getDefaultUnit(){ + return new GeneralUnit(1, unitString); + } + + public boolean contains(Unit u){ + return true; + } + +} -- 2.47.2