updates for 0.9.4
This commit is contained in:
parent
4dff922640
commit
fd4fa42e8a
@ -4,10 +4,10 @@
|
||||
<classpathentry kind="src" path="src-extra"/>
|
||||
<classpathentry kind="src" path="test"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/lib/miglayout15-swing.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JCommon 1.0.16"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JFreeChart 1.0.13"/>
|
||||
<classpathentry kind="lib" path="lib-extra/RXTXcomm.jar"/>
|
||||
<classpathentry kind="lib" path="lib-test/junit-4.7.jar"/>
|
||||
<classpathentry kind="lib" path="lib/jfreechart-1.0.13.jar"/>
|
||||
<classpathentry kind="lib" path="lib/jcommon-1.0.16.jar"/>
|
||||
<classpathentry kind="lib" path="lib/miglayout15-swing.jar"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
17
ChangeLog
17
ChangeLog
@ -1,3 +1,20 @@
|
||||
2009-11-24 Sampo Niskanen
|
||||
|
||||
* Released version 0.9.4
|
||||
|
||||
2009-11-24 Sampo Niskanen
|
||||
|
||||
* Close original window when opening example design
|
||||
|
||||
2009-11-10 Sampo Niskanen
|
||||
|
||||
* [BUG] Fixed transition volume/mass computation
|
||||
* [BUG] Simulations etc. using removed motor configuration IDs
|
||||
|
||||
2009-10-11 Sampo Niskanen
|
||||
|
||||
* [BUG] Sorting motor selection dialog with ',' decimal separator
|
||||
|
||||
2009-10-10 Sampo Niskanen
|
||||
|
||||
* Removed non-ASCII characters from source code files
|
||||
|
@ -1,4 +1,12 @@
|
||||
|
||||
OpenRocket 0.9.4 (2009-11-24):
|
||||
-------------------------------
|
||||
|
||||
Added through-the-wall fin tabs, attaching components to tube
|
||||
couplers, material editing and automatic update checks, and fixed
|
||||
numerous of the most commonly occurring bugs.
|
||||
|
||||
|
||||
OpenRocket 0.9.3 (2009-09-01):
|
||||
-------------------------------
|
||||
|
||||
|
16
TODO
16
TODO
@ -5,21 +5,28 @@ Feature roadmap for OpenRocket 1.0
|
||||
Must-have:
|
||||
|
||||
- Go through thrust curves and correct errors
|
||||
- Add styrofoam and depron materials
|
||||
or
|
||||
- Hide duplicate motors
|
||||
|
||||
|
||||
Bugs:
|
||||
|
||||
- Unit tests fail from ant script
|
||||
|
||||
|
||||
Maybe:
|
||||
|
||||
- Inform user about software updates
|
||||
- Re-investigate 15% reduction of three-fin CNa
|
||||
- Take into account all fins in interference effects
|
||||
- Add slight randomness to yaw moment
|
||||
|
||||
|
||||
Postponed:
|
||||
|
||||
- Integration with thrustcurve.org (syncing?)
|
||||
- Reading thrust curves from external directory
|
||||
- Plot motor thrust curve
|
||||
|
||||
|
||||
- Windows executable wrapper (launch4j)
|
||||
- Allow only one instance of OpenRocket running (RMI communication)
|
||||
- Only schedule rocket figure update instead of each time updating it
|
||||
@ -30,7 +37,6 @@ Postponed:
|
||||
- Simulate other branches
|
||||
- Implement setDefaults() method for RocketComponent
|
||||
- BUG: Inner tube cluster rotation, edit with spinner arrows, slider wrong
|
||||
- Reading thrust curves from external directory
|
||||
- NAR/CNES/etc competition validity checking
|
||||
- Running from command line
|
||||
- Print support
|
||||
@ -79,4 +85,6 @@ In 0.9.4:
|
||||
- Allow editing user-defined materials
|
||||
- [BUG] All configuration dialogs too high
|
||||
- Simulation plot dialog forces dialog one button row too high (All/None)
|
||||
- Add styrofoam and depron materials
|
||||
- Inform user about software updates
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
# The OpenRocket build version
|
||||
|
||||
build.version=0.9.4pre
|
||||
build.version=0.9.4
|
||||
|
||||
|
||||
# The source of the package. When building a package for a specific
|
||||
|
@ -1,7 +1,8 @@
|
||||
; Sachsen Feuerwerk / WECO Feuerwerk A8-3
|
||||
; Created by Sampo Niskanen
|
||||
; Data taken from:
|
||||
; Created by Sampo Niskanen for OpenRocket, released into the Public Domain
|
||||
; Thrust curve data taken from:
|
||||
; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf
|
||||
; Mass measured by the author.
|
||||
A8 18 70 3 0.00312 0.0153 SF
|
||||
0.065 0.44
|
||||
0.11 1.832
|
||||
|
@ -1,7 +1,8 @@
|
||||
; Sachsen Feuerwerk / WECO Feuerwerk B4-0, B4-4
|
||||
; Created by Sampo Niskanen
|
||||
; Data taken from:
|
||||
; Created by Sampo Niskanen for OpenRocket, released into the Public Domain
|
||||
; Thrust curve data taken from:
|
||||
; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf
|
||||
; Mass measured by the author from B4-4 motors.
|
||||
B4 18 70 0-4 0.00833 0.0195 SF
|
||||
0.088 0.542
|
||||
0.167 3.007
|
||||
|
@ -1,8 +1,8 @@
|
||||
; Sachsen Feuerwerk / WECO Feuerwerk Held 1000
|
||||
; Created by Sampo Niskanen
|
||||
; True propellant weight unknown
|
||||
; Data taken from:
|
||||
; Created by Sampo Niskanen for OpenRocket, released into the Public Domain
|
||||
; Thrust curve data taken from:
|
||||
; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf
|
||||
; True propellant weight unknown, estimates from various sources
|
||||
C2 15 95 P 0.012 0.024 SF
|
||||
0.075 3.543
|
||||
0.16 8.231
|
||||
|
@ -1,7 +1,8 @@
|
||||
; Sachsen Feuerwerk / WECO Feuerwerk C6-0, C6-3, C6-5
|
||||
; Created by Sampo Niskanen
|
||||
; Data taken from:
|
||||
; Created by Sampo Niskanen for OpenRocket, released into the Public Domain
|
||||
; Thrust curve data taken from:
|
||||
; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf
|
||||
; Mass measured by the author from C6-3 motors.
|
||||
C6 18 70 0-3-5 0.01248 0.022 SF
|
||||
0.096 0.579
|
||||
0.152 2.441
|
||||
|
@ -1,7 +1,8 @@
|
||||
; Sachsen Feuerwerk / WECO Feuerwerk D7-0, D7-3
|
||||
; Created by Sampo Niskanen
|
||||
; Data taken from:
|
||||
; Created by Sampo Niskanen for OpenRocket, released into the Public Domain
|
||||
; Thrust curve data taken from:
|
||||
; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf
|
||||
; Mass measured by the author from D7-3 motors.
|
||||
D7 25 70 0-3 0.019 0.043 SF
|
||||
0.079 1.625
|
||||
0.179 6.979
|
||||
|
BIN
dists/OpenRocket-0.9.4-src.zip
Normal file
BIN
dists/OpenRocket-0.9.4-src.zip
Normal file
Binary file not shown.
BIN
dists/OpenRocket-0.9.4.jar
Normal file
BIN
dists/OpenRocket-0.9.4.jar
Normal file
Binary file not shown.
@ -8,3 +8,7 @@
|
||||
|
||||
Redirect 307 /actions/reportbug http://sampo.kapsi.fi/openrocket/reportbug.php
|
||||
|
||||
RewriteEngine On
|
||||
RewriteBase /actions/
|
||||
RewriteRule ^updates$ updates.php
|
||||
|
||||
|
@ -31,8 +31,8 @@ header("Content-type: text/plain; charset=utf-8");
|
||||
if (preg_match("/^[a-zA-Z0-9. -]{1,30}$/", $version) &&
|
||||
strlen($content) > 0) {
|
||||
|
||||
if (mail($mailaddr, "Automatic bug report for OpenRocket " . $version,
|
||||
$content . $headers,
|
||||
$subject = date("Y-m-d H:i:s") . " Automatic bug report for OpenRocket " . $version;
|
||||
if (mail($mailaddr, $subject, $content . $headers,
|
||||
"From: Automatic Bug Reports <".$mailaddr.">\r\n".
|
||||
"Content-Type: text/plain; charset=utf-8")) {
|
||||
|
||||
|
65
html/actions/updates.php
Normal file
65
html/actions/updates.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?
|
||||
$logfiles = "/home/groups/o/op/openrocket/persistent/logs/access-";
|
||||
|
||||
|
||||
// getallheaders method
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders() {
|
||||
foreach ($_SERVER as $name => $value) {
|
||||
if (substr($name, 0, 5) == 'HTTP_') {
|
||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Parse + validate headers
|
||||
$orid = "";
|
||||
$orversion = "";
|
||||
$oros = "";
|
||||
$orjava = "";
|
||||
$orcountry = "";
|
||||
foreach (getallheaders() as $header => $value) {
|
||||
if (preg_match("/^[a-zA-Z0-9 !$%&()*+,.\\/:=?@_~-]{1,40}$/", $value)) {
|
||||
$h = strtolower($header);
|
||||
if ($h == 'x-openrocket-version') {
|
||||
$orversion = $value;
|
||||
} else if ($h == 'x-openrocket-id') {
|
||||
$orid = $value;
|
||||
} else if ($h == 'x-openrocket-os') {
|
||||
$oros = $value;
|
||||
} else if ($h == 'x-openrocket-java') {
|
||||
$orjava = $value;
|
||||
} else if ($h == 'x-openrocket-country') {
|
||||
$orcountry = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log the request
|
||||
if (strlen($orversion) > 0 || strlen($orid) > 0 || strlen($oros) > 0
|
||||
|| strlen($orjava) > 0 || strlen($orcountry) > 0) {
|
||||
|
||||
$file = $logfiles . gmdate("Y-m");
|
||||
$line = gmdate("Y-m-d H:i:s") . ";" . $orid . ";" . $orversion .
|
||||
";" . $oros . ";" . $orjava . ";" . $orcountry . "\n";
|
||||
|
||||
$fp = fopen($file, 'a');
|
||||
if ($fp != FALSE) {
|
||||
fwrite($fp, $line);
|
||||
fclose($fp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set HTTP content-type header
|
||||
header("Content-type: text/plain; charset=utf-8");
|
||||
|
||||
$version = $_GET["version"];
|
||||
|
||||
// No updates available
|
||||
header("HTTP/1.0 202 No Content");
|
||||
|
||||
?>
|
@ -90,8 +90,9 @@ public class Databases {
|
||||
BULK_MATERIAL.add(new Material.Bulk("Polystyrene", 1050, false));
|
||||
BULK_MATERIAL.add(new Material.Bulk("PVC", 1390, false));
|
||||
BULK_MATERIAL.add(new Material.Bulk("Spruce", 450, false));
|
||||
BULK_MATERIAL.add(new Material.Bulk("Styrofoam generic (EPS)", 20, false));
|
||||
BULK_MATERIAL.add(new Material.Bulk("Styrofoam / Blue Foam (XPS)", 32, false));
|
||||
BULK_MATERIAL.add(new Material.Bulk("Styrofoam (generic EPS)", 20, false));
|
||||
// BULK_MATERIAL.add(new Material.Bulk("Styrofoam (Blue foam, XPS)", 32, false));
|
||||
BULK_MATERIAL.add(new Material.Bulk("Styrofoam \"Blue foam\" (XPS)", 32, false));
|
||||
BULK_MATERIAL.add(new Material.Bulk("Quantum tubing",1050, false));
|
||||
|
||||
SURFACE_MATERIAL.add(new Material.Surface("Ripstop nylon", 0.067, false));
|
||||
|
@ -10,7 +10,9 @@ import java.util.List;
|
||||
|
||||
import net.sf.openrocket.motor.Manufacturer;
|
||||
import net.sf.openrocket.motor.Motor;
|
||||
import net.sf.openrocket.motor.MotorDigest;
|
||||
import net.sf.openrocket.motor.ThrustCurveMotor;
|
||||
import net.sf.openrocket.motor.MotorDigest.DataType;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
|
||||
public class RASPMotorLoader extends MotorLoader {
|
||||
@ -195,11 +197,19 @@ public class RASPMotorLoader extends MotorLoader {
|
||||
|
||||
designation = removeDelay(designation);
|
||||
|
||||
// Create the motor digest from data available in RASP files
|
||||
MotorDigest motorDigest = new MotorDigest();
|
||||
motorDigest.update(DataType.TIME_ARRAY, timeArray);
|
||||
motorDigest.update(DataType.MASS_SPECIFIC, totalW, totalW-propW);
|
||||
motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
|
||||
final String digest = motorDigest.getDigest();
|
||||
|
||||
|
||||
try {
|
||||
|
||||
return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer),
|
||||
designation, comment, Motor.Type.UNKNOWN,
|
||||
delays, diameter, length, timeArray, thrustArray, cgArray);
|
||||
delays, diameter, length, timeArray, thrustArray, cgArray, digest);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
|
@ -14,7 +14,9 @@ import net.sf.openrocket.file.simplesax.PlainTextHandler;
|
||||
import net.sf.openrocket.file.simplesax.SimpleSAX;
|
||||
import net.sf.openrocket.motor.Manufacturer;
|
||||
import net.sf.openrocket.motor.Motor;
|
||||
import net.sf.openrocket.motor.MotorDigest;
|
||||
import net.sf.openrocket.motor.ThrustCurveMotor;
|
||||
import net.sf.openrocket.motor.MotorDigest.DataType;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
|
||||
import org.xml.sax.InputSource;
|
||||
@ -321,6 +323,11 @@ public class RockSimMotorLoader extends MotorLoader {
|
||||
finalizeThrustCurve(time, force, mass, cg);
|
||||
final int n = time.size();
|
||||
|
||||
if (hasIllegalValue(mass))
|
||||
calculateMass = true;
|
||||
if (hasIllegalValue(cg))
|
||||
calculateCG = true;
|
||||
|
||||
if (calculateMass) {
|
||||
mass = calculateMass(time, force, initMass, propMass);
|
||||
}
|
||||
@ -330,19 +337,33 @@ public class RockSimMotorLoader extends MotorLoader {
|
||||
}
|
||||
}
|
||||
|
||||
double[] timeArray = new double[n];
|
||||
double[] thrustArray = new double[n];
|
||||
double[] timeArray = toArray(time);
|
||||
double[] thrustArray = toArray(force);
|
||||
Coordinate[] cgArray = new Coordinate[n];
|
||||
for (int i=0; i < n; i++) {
|
||||
timeArray[i] = time.get(i);
|
||||
thrustArray[i] = force.get(i);
|
||||
cgArray[i] = new Coordinate(cg.get(i),0,0,mass.get(i));
|
||||
}
|
||||
|
||||
|
||||
// Create the motor digest from all data available in the file
|
||||
MotorDigest motorDigest = new MotorDigest();
|
||||
motorDigest.update(DataType.TIME_ARRAY, timeArray);
|
||||
if (!calculateMass) {
|
||||
motorDigest.update(DataType.MASS_PER_TIME, toArray(mass));
|
||||
} else {
|
||||
motorDigest.update(DataType.MASS_SPECIFIC, initMass, initMass-propMass);
|
||||
}
|
||||
if (!calculateCG) {
|
||||
motorDigest.update(DataType.CG_PER_TIME, toArray(cg));
|
||||
}
|
||||
motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
|
||||
final String digest = motorDigest.getDigest();
|
||||
|
||||
|
||||
try {
|
||||
return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer),
|
||||
designation, description, type,
|
||||
delays, diameter, length, timeArray, thrustArray, cgArray);
|
||||
delays, diameter, length, timeArray, thrustArray, cgArray, digest);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SAXException("Illegal motor data", e);
|
||||
}
|
||||
@ -417,4 +438,24 @@ public class RockSimMotorLoader extends MotorLoader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static boolean hasIllegalValue(List<Double> list) {
|
||||
for (Double d: list) {
|
||||
if (d == null || d.isNaN() || d.isInfinite()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static double[] toArray(List<Double> list) {
|
||||
final int n = list.size();
|
||||
double[] array = new double[n];
|
||||
for (int i=0; i < n; i++) {
|
||||
array[i] = list.get(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
@ -379,16 +379,17 @@ public class DoubleModel implements ChangeListener, ChangeSource {
|
||||
}
|
||||
|
||||
// Implement a wrapper to the ChangeListeners
|
||||
ArrayList<PropertyChangeListener> listeners = new ArrayList<PropertyChangeListener>();
|
||||
ArrayList<PropertyChangeListener> propertyChangeListeners =
|
||||
new ArrayList<PropertyChangeListener>();
|
||||
@Override
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
listeners.add(listener);
|
||||
propertyChangeListeners.add(listener);
|
||||
DoubleModel.this.addChangeListener(this);
|
||||
}
|
||||
@Override
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
listeners.remove(listener);
|
||||
if (listeners.isEmpty())
|
||||
propertyChangeListeners.remove(listener);
|
||||
if (propertyChangeListeners.isEmpty())
|
||||
DoubleModel.this.removeChangeListener(this);
|
||||
}
|
||||
// If the value has changed, generate an event to the listeners
|
||||
@ -399,7 +400,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
|
||||
PropertyChangeEvent event = new PropertyChangeEvent(this,Action.SELECTED_KEY,
|
||||
oldValue,newValue);
|
||||
oldValue = newValue;
|
||||
Object[] l = listeners.toArray();
|
||||
Object[] l = propertyChangeListeners.toArray();
|
||||
for (int i=0; i<l.length; i++) {
|
||||
((PropertyChangeListener)l[i]).propertyChange(event);
|
||||
}
|
||||
@ -619,7 +620,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("BUG: Unable to invoke setMethod of "+this, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("BUG: Unable to invoke setMethod of "+this, e);
|
||||
throw new RuntimeException("Setter method of "+this+" threw exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ public class EnumModel<T extends Enum<T>> extends AbstractListModel
|
||||
}
|
||||
|
||||
if (!(item instanceof Enum<?>)) {
|
||||
throw new IllegalArgumentException("Not String or Enum");
|
||||
throw new IllegalArgumentException("Not String or Enum, item="+item);
|
||||
}
|
||||
|
||||
// Comparison with == ok, since both are enums
|
||||
|
@ -450,6 +450,12 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
if (!(o instanceof String))
|
||||
return;
|
||||
|
||||
if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length ||
|
||||
columnIndex < 0 || columnIndex >= Columns.values().length) {
|
||||
throw new IllegalArgumentException("Index out of bounds, row="+rowIndex+
|
||||
" column="+columnIndex+" fin point count="+finset.getFinPoints().length);
|
||||
}
|
||||
|
||||
String str = (String)o;
|
||||
try {
|
||||
|
||||
@ -466,7 +472,5 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
} catch (IllegalFinPointException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -50,14 +50,14 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig {
|
||||
|
||||
JPanel tab;
|
||||
|
||||
tab = positionTab();
|
||||
tabbedPane.insertTab("Radial position", null, tab, "Radial position", 1);
|
||||
|
||||
tab = new MotorConfig((MotorMount)c);
|
||||
tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 2);
|
||||
tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 1);
|
||||
|
||||
tab = clusterTab();
|
||||
tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 3);
|
||||
tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 2);
|
||||
|
||||
tab = positionTab();
|
||||
tabbedPane.insertTab("Radial position", null, tab, "Radial position", 3);
|
||||
|
||||
tabbedPane.setSelectedIndex(0);
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ import javax.swing.JTextArea;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.communication.BugReporter;
|
||||
import net.sf.openrocket.gui.components.StyledLabel;
|
||||
import net.sf.openrocket.gui.components.SelectableLabel;
|
||||
import net.sf.openrocket.gui.components.StyledLabel;
|
||||
import net.sf.openrocket.util.GUIUtil;
|
||||
import net.sf.openrocket.util.JarUtil;
|
||||
import net.sf.openrocket.util.Prefs;
|
||||
@ -179,8 +179,8 @@ public class BugReportDialog extends JDialog {
|
||||
|
||||
BugReportDialog reportDialog =
|
||||
new BugReportDialog(parent,
|
||||
"<html>You can report a bug in OpenRocket by filling in and submitting " +
|
||||
"the form below.<br>" +
|
||||
"<html><b>You can report a bug in OpenRocket by filling in and submitting " +
|
||||
"the form below.</b><br>" +
|
||||
"You can also report bugs and include attachments on the project " +
|
||||
"web site.", sb.toString());
|
||||
reportDialog.setVisible(true);
|
||||
@ -239,8 +239,8 @@ public class BugReportDialog extends JDialog {
|
||||
sb.append('\n');
|
||||
|
||||
BugReportDialog reportDialog =
|
||||
new BugReportDialog(parent, "Please include a short description about " +
|
||||
"what you were doing when the exception occurred.", sb.toString());
|
||||
new BugReportDialog(parent, "<html><b>Please include a short description about " +
|
||||
"what you were doing when the exception occurred.</b>", sb.toString());
|
||||
reportDialog.setVisible(true);
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,6 @@ import java.awt.event.MouseEvent;
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
@ -40,6 +37,8 @@ import net.sf.openrocket.database.Databases;
|
||||
import net.sf.openrocket.gui.components.StyledLabel;
|
||||
import net.sf.openrocket.motor.Motor;
|
||||
import net.sf.openrocket.unit.UnitGroup;
|
||||
import net.sf.openrocket.unit.Value;
|
||||
import net.sf.openrocket.unit.ValueComparator;
|
||||
import net.sf.openrocket.util.GUIUtil;
|
||||
import net.sf.openrocket.util.Prefs;
|
||||
|
||||
@ -383,10 +382,6 @@ public class MotorChooserDialog extends JDialog {
|
||||
public String getValue(Motor m) {
|
||||
return m.getManufacturer().getDisplayName();
|
||||
}
|
||||
// @Override
|
||||
// public String getToolTipText(Motor m) {
|
||||
// return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
|
||||
// }
|
||||
@Override
|
||||
public Comparator<?> getComparator() {
|
||||
return Collator.getInstance();
|
||||
@ -397,10 +392,6 @@ public class MotorChooserDialog extends JDialog {
|
||||
public String getValue(Motor m) {
|
||||
return m.getDesignation();
|
||||
}
|
||||
// @Override
|
||||
// public String getToolTipText(Motor m) {
|
||||
// return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
|
||||
// }
|
||||
@Override
|
||||
public Comparator<?> getComparator() {
|
||||
return Motor.getDesignationComparator();
|
||||
@ -411,10 +402,6 @@ public class MotorChooserDialog extends JDialog {
|
||||
public String getValue(Motor m) {
|
||||
return m.getMotorType().getName();
|
||||
}
|
||||
// @Override
|
||||
// public String getToolTipText(Motor m) {
|
||||
// return m.getMotorType().getDescription();
|
||||
// }
|
||||
@Override
|
||||
public Comparator<?> getComparator() {
|
||||
return Collator.getInstance();
|
||||
@ -422,46 +409,42 @@ public class MotorChooserDialog extends JDialog {
|
||||
},
|
||||
DIAMETER("Diameter") {
|
||||
@Override
|
||||
public String getValue(Motor m) {
|
||||
return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
|
||||
m.getDiameter());
|
||||
public Object getValue(Motor m) {
|
||||
return new Value(m.getDiameter(), UnitGroup.UNITS_MOTOR_DIMENSIONS);
|
||||
}
|
||||
@Override
|
||||
public Comparator<?> getComparator() {
|
||||
return getNumericalComparator();
|
||||
return ValueComparator.INSTANCE;
|
||||
}
|
||||
},
|
||||
LENGTH("Length") {
|
||||
@Override
|
||||
public String getValue(Motor m) {
|
||||
return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
|
||||
m.getLength());
|
||||
public Object getValue(Motor m) {
|
||||
return new Value(m.getLength(), UnitGroup.UNITS_MOTOR_DIMENSIONS);
|
||||
}
|
||||
@Override
|
||||
public Comparator<?> getComparator() {
|
||||
return getNumericalComparator();
|
||||
return ValueComparator.INSTANCE;
|
||||
}
|
||||
},
|
||||
IMPULSE("Impulse") {
|
||||
@Override
|
||||
public String getValue(Motor m) {
|
||||
return UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
|
||||
m.getTotalImpulse());
|
||||
public Object getValue(Motor m) {
|
||||
return new Value(m.getTotalImpulse(), UnitGroup.UNITS_IMPULSE);
|
||||
}
|
||||
@Override
|
||||
public Comparator<?> getComparator() {
|
||||
return getNumericalComparator();
|
||||
return ValueComparator.INSTANCE;
|
||||
}
|
||||
},
|
||||
TIME("Burn time") {
|
||||
@Override
|
||||
public String getValue(Motor m) {
|
||||
return UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
|
||||
m.getAverageTime());
|
||||
public Object getValue(Motor m) {
|
||||
return new Value(m.getAverageTime(), UnitGroup.UNITS_SHORT_TIME);
|
||||
}
|
||||
@Override
|
||||
public Comparator<?> getComparator() {
|
||||
return getNumericalComparator();
|
||||
return ValueComparator.INSTANCE;
|
||||
}
|
||||
};
|
||||
|
||||
@ -479,7 +462,7 @@ public class MotorChooserDialog extends JDialog {
|
||||
}
|
||||
|
||||
|
||||
public abstract String getValue(Motor m);
|
||||
public abstract Object getValue(Motor m);
|
||||
public abstract Comparator<?> getComparator();
|
||||
|
||||
public String getTitle() {
|
||||
@ -623,7 +606,7 @@ public class MotorChooserDialog extends JDialog {
|
||||
public boolean filterByString(Motor m) {
|
||||
main: for (String s : searchTerms) {
|
||||
for (MotorColumns col : MotorColumns.values()) {
|
||||
String str = col.getValue(m).toLowerCase();
|
||||
String str = col.getValue(m).toString().toLowerCase();
|
||||
if (str.indexOf(s) >= 0)
|
||||
continue main;
|
||||
}
|
||||
@ -664,35 +647,4 @@ public class MotorChooserDialog extends JDialog {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Comparator<String> numericalComparator = null;
|
||||
private static Comparator<String> getNumericalComparator() {
|
||||
if (numericalComparator == null)
|
||||
numericalComparator = new NumericalComparator();
|
||||
return numericalComparator;
|
||||
}
|
||||
|
||||
private static class NumericalComparator implements Comparator<String> {
|
||||
private Pattern pattern =
|
||||
Pattern.compile("^\\s*([0-9]*[.,][0-9]+|[0-9]+[.,]?[0-9]*).*?$");
|
||||
private Collator collator = null;
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
Matcher m1, m2;
|
||||
|
||||
m1 = pattern.matcher(s1);
|
||||
m2 = pattern.matcher(s2);
|
||||
if (m1.find() && m2.find()) {
|
||||
double d1 = Double.parseDouble(m1.group(1));
|
||||
double d2 = Double.parseDouble(m2.group(1));
|
||||
|
||||
return (int)((d1-d2)*1000);
|
||||
}
|
||||
|
||||
if (collator == null)
|
||||
collator = Collator.getInstance(Locale.US);
|
||||
return collator.compare(s1, s2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ public class PreferencesDialog extends JDialog {
|
||||
"Delete", "Confirm", true)), "wrap 40lp, growx, sg combos");
|
||||
|
||||
|
||||
final JCheckBox softwareUpdateBox = new JCheckBox("Check for software updates");
|
||||
final JCheckBox softwareUpdateBox = new JCheckBox("Check for software updates at startup");
|
||||
softwareUpdateBox.setSelected(Prefs.getCheckUpdates());
|
||||
softwareUpdateBox.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
|
@ -752,7 +752,7 @@ public class BasicFrame extends JFrame {
|
||||
|
||||
|
||||
|
||||
private static boolean open(URL url, Window parent) {
|
||||
private static boolean open(URL url, BasicFrame parent) {
|
||||
String filename = null;
|
||||
|
||||
// Try using URI.getPath();
|
||||
@ -780,7 +780,13 @@ public class BasicFrame extends JFrame {
|
||||
|
||||
try {
|
||||
InputStream is = url.openStream();
|
||||
open(is, filename, parent);
|
||||
if (open(is, filename, parent)) {
|
||||
// Close previous window if replacing
|
||||
if (parent.replaceable && parent.document.isSaved()) {
|
||||
parent.closeAction();
|
||||
parent.replaceable = false;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
JOptionPane.showMessageDialog(parent,
|
||||
"An error occurred while opening the file " + filename,
|
||||
|
@ -163,7 +163,7 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
|
||||
// Normal exception, show question dialog
|
||||
String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
|
||||
if (msg.length() > 90) {
|
||||
msg = msg.substring(0, 90) + "...";
|
||||
msg = msg.substring(0, 80) + "...";
|
||||
}
|
||||
|
||||
|
||||
|
@ -98,6 +98,7 @@ public abstract class Motor implements Comparable<Motor> {
|
||||
private final String designation;
|
||||
private final String description;
|
||||
private final Type motorType;
|
||||
private final String digest;
|
||||
|
||||
private final double[] delays;
|
||||
|
||||
@ -126,7 +127,7 @@ public abstract class Motor implements Comparable<Motor> {
|
||||
* @param length length of the motor
|
||||
*/
|
||||
protected Motor(Manufacturer manufacturer, String designation, String description,
|
||||
Type type, double[] delays, double diameter, double length) {
|
||||
Type type, double[] delays, double diameter, double length, String digest) {
|
||||
|
||||
if (manufacturer == null || designation == null || description == null ||
|
||||
type == null || delays == null) {
|
||||
@ -140,6 +141,7 @@ public abstract class Motor implements Comparable<Motor> {
|
||||
this.delays = delays.clone();
|
||||
this.diameter = diameter;
|
||||
this.length = length;
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
|
||||
@ -435,6 +437,20 @@ public abstract class Motor implements Comparable<Motor> {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a digest string of this motor. This digest should be computed from all
|
||||
* flight-affecting data. For example for thrust curve motors the thrust curve
|
||||
* should be digested using suitable precision. The intention is that the combination
|
||||
* of motor type, manufacturer, designation, diameter, length and digest uniquely
|
||||
* identify any particular motor data file.
|
||||
*
|
||||
* @return a string digest of this motor (0-60 chars)
|
||||
*/
|
||||
public String getDigestString() {
|
||||
return digest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares two <code>Motor</code> objects. The motors are considered equal
|
||||
* if they have identical manufacturers, designations and types, near-identical
|
||||
|
141
src/net/sf/openrocket/motor/MotorDigest.java
Normal file
141
src/net/sf/openrocket/motor/MotorDigest.java
Normal file
@ -0,0 +1,141 @@
|
||||
package net.sf.openrocket.motor;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import net.sf.openrocket.util.TextUtil;
|
||||
|
||||
public class MotorDigest {
|
||||
|
||||
private static final double EPSILON = 0.00000000001;
|
||||
|
||||
public enum DataType {
|
||||
/** An array of time points at which data is available (in ms) */
|
||||
TIME_ARRAY(0, 1000),
|
||||
/** Mass data for a few specific points (normally initial and empty mass) (in 0.1g) */
|
||||
MASS_SPECIFIC(1, 10000),
|
||||
/** Mass per time (in 0.1g) */
|
||||
MASS_PER_TIME(2, 10000),
|
||||
/** CG position for a few specific points (normally initial and final CG) (in mm) */
|
||||
CG_SPECIFIC(3, 1000),
|
||||
/** CG position per time (in mm) */
|
||||
CG_PER_TIME(4, 1000),
|
||||
/** Thrust force per time (in mN) */
|
||||
FORCE_PER_TIME(5, 1000);
|
||||
|
||||
private final int order;
|
||||
private final int multiplier;
|
||||
DataType(int order, int multiplier) {
|
||||
this.order = order;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
public int getMultiplier() {
|
||||
return multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final MessageDigest digest;
|
||||
private boolean used = false;
|
||||
private int lastOrder = -1;
|
||||
|
||||
|
||||
public MotorDigest() {
|
||||
try {
|
||||
digest = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("MD5 digest not supported by JRE", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void update(DataType type, int ... values) {
|
||||
|
||||
// Check for correct order
|
||||
if (lastOrder >= type.getOrder()) {
|
||||
throw new IllegalArgumentException("Called with type="+type+" order="+type.getOrder()+
|
||||
" while lastOrder=" + lastOrder);
|
||||
}
|
||||
lastOrder = type.getOrder();
|
||||
|
||||
// Digest the type
|
||||
digest.update(bytes(type.getOrder()));
|
||||
|
||||
// Digest the data length
|
||||
digest.update(bytes(values.length));
|
||||
|
||||
// Digest the values
|
||||
for (int v: values) {
|
||||
digest.update(bytes(v));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void update(DataType type, int multiplier, double ... values) {
|
||||
|
||||
int[] intValues = new int[values.length];
|
||||
for (int i=0; i<values.length; i++) {
|
||||
double v = values[i];
|
||||
v = next(v);
|
||||
v *= multiplier;
|
||||
v = next(v);
|
||||
intValues[i] = (int) Math.round(v);
|
||||
}
|
||||
update(type, intValues);
|
||||
}
|
||||
|
||||
public void update(DataType type, double ... values) {
|
||||
update(type, type.getMultiplier(), values);
|
||||
}
|
||||
|
||||
private static double next(double v) {
|
||||
return v + Math.signum(v) * EPSILON;
|
||||
}
|
||||
|
||||
|
||||
public String getDigest() {
|
||||
if (used) {
|
||||
throw new IllegalStateException("MotorDigest already used");
|
||||
}
|
||||
used = true;
|
||||
byte[] result = digest.digest();
|
||||
return TextUtil.hexString(result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private byte[] bytes(int value) {
|
||||
return new byte[] {
|
||||
(byte) ((value>>>24) & 0xFF), (byte) ((value>>>16) & 0xFF),
|
||||
(byte) ((value>>>8) & 0xFF), (byte) (value & 0xFF)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static String digestComment(String comment) {
|
||||
comment = comment.replaceAll("\\s+", " ").trim();
|
||||
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("MD5 digest not supported by JRE", e);
|
||||
}
|
||||
|
||||
try {
|
||||
digest.update(comment.getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("UTF-8 encoding not supported by JRE", e);
|
||||
}
|
||||
|
||||
return TextUtil.hexString(digest.digest());
|
||||
}
|
||||
|
||||
}
|
@ -35,8 +35,8 @@ public class ThrustCurveMotor extends Motor {
|
||||
*/
|
||||
public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description,
|
||||
Motor.Type type, double[] delays, double diameter, double length,
|
||||
double[] time, double[] thrust, Coordinate[] cg) {
|
||||
super(manufacturer, designation, description, type, delays, diameter, length);
|
||||
double[] time, double[] thrust, Coordinate[] cg, String digest) {
|
||||
super(manufacturer, designation, description, type, delays, diameter, length, digest);
|
||||
|
||||
double max = -1;
|
||||
|
||||
@ -157,5 +157,5 @@ public class ThrustCurveMotor extends Motor {
|
||||
public Coordinate[] getCGPoints() {
|
||||
return cg.clone();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -290,11 +290,26 @@ public class BodyTube extends SymmetricComponent implements MotorMount {
|
||||
|
||||
@Override
|
||||
public Motor getMotor(String id) {
|
||||
if (id == null)
|
||||
return null;
|
||||
|
||||
// Check whether the id is valid for the current rocket
|
||||
RocketComponent root = this.getRoot();
|
||||
if (!(root instanceof Rocket))
|
||||
return null;
|
||||
if (!((Rocket) root).isMotorConfigurationID(id))
|
||||
return null;
|
||||
|
||||
return motors.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMotor(String id, Motor motor) {
|
||||
if (id == null) {
|
||||
if (motor != null) {
|
||||
throw new IllegalArgumentException("Cannot set non-null motor for id null");
|
||||
}
|
||||
}
|
||||
Motor current = motors.get(id);
|
||||
if ((motor == null && current == null) ||
|
||||
(motor != null && motor.equals(current)))
|
||||
|
@ -124,7 +124,7 @@ public class FreeformFinSet extends FinSet {
|
||||
|
||||
/**
|
||||
* Remove the fin point with the given index. The first and last fin points
|
||||
* cannot be removed, and will cause an <code>IllegalArgumentException</code>
|
||||
* cannot be removed, and will cause an <code>IllegalFinPointException</code>
|
||||
* if attempted.
|
||||
*
|
||||
* @param index the fin point index to remove
|
||||
@ -173,8 +173,8 @@ public class FreeformFinSet extends FinSet {
|
||||
* <p>
|
||||
* Note that this method enforces basic fin shape restrictions (non-negative y,
|
||||
* first and last point locations) silently, but throws an
|
||||
* <code>IllegalArgumentException</code> if the point causes fin segments to
|
||||
* intersect. The calling method should always catch this exception.
|
||||
* <code>IllegalFinPointException</code> if the point causes fin segments to
|
||||
* intersect.
|
||||
* <p>
|
||||
* Moving of the first point in the X-axis is allowed, but this actually moves
|
||||
* all of the other points the corresponding distance back.
|
||||
|
@ -201,11 +201,26 @@ implements Clusterable, RadialParent, MotorMount {
|
||||
|
||||
@Override
|
||||
public Motor getMotor(String id) {
|
||||
if (id == null)
|
||||
return null;
|
||||
|
||||
// Check whether the id is valid for the current rocket
|
||||
RocketComponent root = this.getRoot();
|
||||
if (!(root instanceof Rocket))
|
||||
return null;
|
||||
if (!((Rocket) root).isMotorConfigurationID(id))
|
||||
return null;
|
||||
|
||||
return motors.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMotor(String id, Motor motor) {
|
||||
if (id == null) {
|
||||
if (motor != null) {
|
||||
throw new IllegalArgumentException("Cannot set non-null motor for id null");
|
||||
}
|
||||
}
|
||||
Motor current = motors.get(id);
|
||||
if ((motor == null && current == null) ||
|
||||
(motor != null && motor.equals(current)))
|
||||
@ -228,6 +243,7 @@ implements Clusterable, RadialParent, MotorMount {
|
||||
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public int getMotorCount() {
|
||||
return getClusterCount();
|
||||
|
@ -88,7 +88,8 @@ public interface MotorMount extends ChangeSource {
|
||||
/**
|
||||
* Return the motor for the motor configuration. May return <code>null</code>
|
||||
* if no motor has been set. This method must return <code>null</code> if ID
|
||||
* is <code>null</code>.
|
||||
* is <code>null</code> or if the ID is not valid for the current rocket
|
||||
* (or if the component is not part of any rocket).
|
||||
*
|
||||
* @param id the motor configuration ID
|
||||
* @return the motor, or <code>null</code> if not set.
|
||||
@ -107,8 +108,11 @@ public interface MotorMount extends ChangeSource {
|
||||
/**
|
||||
* Get the number of similar motors clustered.
|
||||
*
|
||||
* TODO: HIGH: This should not be used, since the components themselves can be clustered
|
||||
*
|
||||
* @return the number of motors.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getMotorCount();
|
||||
|
||||
|
||||
|
@ -563,6 +563,9 @@ public class Rocket extends RocketComponent {
|
||||
* @return whether any motors are defined for it.
|
||||
*/
|
||||
public boolean hasMotors(String id) {
|
||||
if (id == null)
|
||||
return false;
|
||||
|
||||
Iterator<RocketComponent> iterator = this.deepIterator();
|
||||
while (iterator.hasNext()) {
|
||||
RocketComponent c = iterator.next();
|
||||
@ -588,6 +591,8 @@ public class Rocket extends RocketComponent {
|
||||
* @return the configuration name
|
||||
*/
|
||||
public String getMotorConfigurationName(String id) {
|
||||
if (!isMotorConfigurationID(id))
|
||||
return "";
|
||||
String s = motorConfigurationNames.get(id);
|
||||
if (s == null)
|
||||
return "";
|
||||
@ -607,7 +612,7 @@ public class Rocket extends RocketComponent {
|
||||
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return either the motor configuration name (if set) or its description.
|
||||
*
|
||||
@ -617,10 +622,10 @@ public class Rocket extends RocketComponent {
|
||||
public String getMotorConfigurationNameOrDescription(String id) {
|
||||
String name;
|
||||
|
||||
name = motorConfigurationNames.get(id);
|
||||
name = getMotorConfigurationName(id);
|
||||
if (name != null && !name.equals(""))
|
||||
return name;
|
||||
|
||||
|
||||
return getMotorConfigurationDescription(id);
|
||||
}
|
||||
|
||||
@ -636,10 +641,6 @@ public class Rocket extends RocketComponent {
|
||||
String name;
|
||||
int motorCount = 0;
|
||||
|
||||
if (!motorConfigurationIDs.contains(id)) {
|
||||
throw new IllegalArgumentException("Motor configuration ID does not exist: "+id);
|
||||
}
|
||||
|
||||
// Generate the description
|
||||
|
||||
// First iterate over each stage and store the designations of each motor
|
||||
|
@ -308,7 +308,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
|
||||
} else {
|
||||
// Hollow piece
|
||||
final double height = thickness*hyp/l;
|
||||
dV = pil*height*(r1+r2-height);
|
||||
dV = MathUtil.max(pil*height*(r1+r2-height), 0);
|
||||
}
|
||||
|
||||
// Add to the volume-related components
|
||||
@ -334,11 +334,14 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
|
||||
if (planArea > 0)
|
||||
planCenter /= planArea;
|
||||
|
||||
if (volume == 0) {
|
||||
cg = Coordinate.NUL;
|
||||
if (volume < 0.0000000001) { // 0.1 mm^3
|
||||
volume = 0;
|
||||
cg = new Coordinate(length/2, 0, 0, 0);
|
||||
} else {
|
||||
// getComponentMass is safe now
|
||||
cg = new Coordinate(cgx/volume,0,0,getComponentMass());
|
||||
// Use super.getComponentMass() to ensure only the transition shape mass
|
||||
// is used, not the shoulders
|
||||
cg = new Coordinate(cgx/volume,0,0,super.getComponentMass());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,6 +171,9 @@ public class Transition extends SymmetricComponent {
|
||||
}
|
||||
|
||||
public void setType(Shape type) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("BUG: setType called with null argument");
|
||||
}
|
||||
if (this.type == type)
|
||||
return;
|
||||
this.type = type;
|
||||
|
25
src/net/sf/openrocket/unit/FrequencyUnit.java
Normal file
25
src/net/sf/openrocket/unit/FrequencyUnit.java
Normal file
@ -0,0 +1,25 @@
|
||||
package net.sf.openrocket.unit;
|
||||
|
||||
public class FrequencyUnit extends GeneralUnit {
|
||||
|
||||
|
||||
public FrequencyUnit(double multiplier, String unit) {
|
||||
super(multiplier, unit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public double toUnit(double value) {
|
||||
double hz = 1/value;
|
||||
return hz / multiplier;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double fromUnit(double value) {
|
||||
double hz = value * multiplier;
|
||||
return 1/hz;
|
||||
}
|
||||
|
||||
}
|
@ -57,6 +57,8 @@ public class UnitGroup {
|
||||
|
||||
public static final UnitGroup UNITS_COEFFICIENT;
|
||||
|
||||
// public static final UnitGroup UNITS_FREQUENCY;
|
||||
|
||||
|
||||
public static final Map<String, UnitGroup> UNITS;
|
||||
|
||||
@ -203,6 +205,16 @@ public class UnitGroup {
|
||||
UNITS_COEFFICIENT = new UnitGroup();
|
||||
UNITS_COEFFICIENT.addUnit(new FixedPrecisionUnit(""+ZWSP, 0.01)); // zero-width space
|
||||
|
||||
|
||||
// This is not used by OpenRocket, and not extensively tested:
|
||||
// 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);
|
||||
|
||||
|
||||
HashMap<String,UnitGroup> map = new HashMap<String,UnitGroup>();
|
||||
map.put("NONE", UNITS_NONE);
|
||||
|
@ -27,6 +27,18 @@ public class Value implements Comparable<Value> {
|
||||
this.value = value;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new Value object using unit group. Currently it simply uses the default
|
||||
* unit of the group, but may later change.
|
||||
*
|
||||
* @param value the value to set.
|
||||
* @param group the group the value belongs to.
|
||||
*/
|
||||
public Value(double value, UnitGroup group) {
|
||||
this(value, group.getDefaultUnit());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
14
src/net/sf/openrocket/unit/ValueComparator.java
Normal file
14
src/net/sf/openrocket/unit/ValueComparator.java
Normal file
@ -0,0 +1,14 @@
|
||||
package net.sf.openrocket.unit;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class ValueComparator implements Comparator<Value> {
|
||||
|
||||
public static final ValueComparator INSTANCE = new ValueComparator();
|
||||
|
||||
@Override
|
||||
public int compare(Value o1, Value o2) {
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
|
||||
}
|
@ -16,6 +16,18 @@ public class Base64 {
|
||||
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
|
||||
};
|
||||
private static final char PAD = '=';
|
||||
|
||||
// private static final byte[] REVERSE;
|
||||
// static {
|
||||
// REVERSE = new byte[128];
|
||||
// Arrays.fill(REVERSE, (byte)-1);
|
||||
// for (int i=0; i<64; i++) {
|
||||
// REVERSE[ALPHABET[i]] = (byte)i;
|
||||
// }
|
||||
// REVERSE['-'] = 62;
|
||||
// REVERSE['_'] = 63;
|
||||
// REVERSE[PAD] = 0;
|
||||
// }
|
||||
|
||||
private static final Map<Character,Integer> REVERSE = new HashMap<Character,Integer>();
|
||||
static {
|
||||
|
@ -203,7 +203,7 @@ public class Prefs {
|
||||
public static String getUniqueID() {
|
||||
String id = PREFNODE.get("id", null);
|
||||
if (id == null) {
|
||||
id = UniqueID.generateHashedID();
|
||||
id = UniqueID.uuid();
|
||||
PREFNODE.put("id", id);
|
||||
}
|
||||
return id;
|
||||
|
@ -2,6 +2,28 @@ package net.sf.openrocket.util;
|
||||
|
||||
|
||||
public class TextUtil {
|
||||
private static final char[] HEX = {
|
||||
'0','1','2','3','4','5','6','7',
|
||||
'8','9','a','b','c','d','e','f'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the bytes formatted as a hexadecimal string. The length of the
|
||||
* string will be twice the number of bytes, with no spacing between the bytes
|
||||
* and lowercase letters utilized.
|
||||
*
|
||||
* @param bytes the bytes to convert.
|
||||
* @return the bytes in hexadecimal notation.
|
||||
*/
|
||||
public static final String hexString(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder(bytes.length * 2);
|
||||
for (byte b: bytes) {
|
||||
sb.append(HEX[(b >>> 4) & 0xF]);
|
||||
sb.append(HEX[b & 0xF]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string of the double value with suitable precision (5 digits).
|
||||
|
@ -1,12 +1,8 @@
|
||||
package net.sf.openrocket.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.sf.openrocket.gui.main.ExceptionHandler;
|
||||
|
||||
public class UniqueID {
|
||||
|
||||
private static AtomicInteger nextId = new AtomicInteger(1);
|
||||
@ -34,34 +30,4 @@ public class UniqueID {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a hashed unique ID that contains no information whatsoever of the
|
||||
* originating computer.
|
||||
*
|
||||
* @return a unique identifier string that contains no information about the computer.
|
||||
*/
|
||||
public static String generateHashedID() {
|
||||
String id = UUID.randomUUID().toString();
|
||||
|
||||
try {
|
||||
MessageDigest algorithm = MessageDigest.getInstance("MD5");
|
||||
algorithm.reset();
|
||||
algorithm.update(id.getBytes());
|
||||
byte[] digest = algorithm.digest();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b: digest) {
|
||||
sb.append(String.format("%02X", 0xFF & b));
|
||||
}
|
||||
id = sb.toString();
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
ExceptionHandler.handleErrorCondition(e);
|
||||
id = "" + id.hashCode();
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ public class MotorPrinter {
|
||||
System.out.printf(" Total impulse: %.2f Ns\n", m.getTotalImpulse());
|
||||
System.out.println(" Diameter: " + m.getDiameter()*1000 + " mm");
|
||||
System.out.println(" Length: " + m.getLength()*1000 + " mm");
|
||||
System.out.println(" Digest: " + m.getDigestString());
|
||||
|
||||
if (m instanceof ThrustCurveMotor) {
|
||||
ThrustCurveMotor tc = (ThrustCurveMotor)m;
|
||||
|
76
test/net/sf/openrocket/motor/MotorDigestTest.java
Normal file
76
test/net/sf/openrocket/motor/MotorDigestTest.java
Normal file
@ -0,0 +1,76 @@
|
||||
package net.sf.openrocket.motor;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import net.sf.openrocket.motor.MotorDigest.DataType;
|
||||
import net.sf.openrocket.util.TextUtil;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class MotorDigestTest {
|
||||
|
||||
private static final double[] timeArray = {
|
||||
0.0, 0.123456789, 0.4115, Math.nextAfter(Math.nextAfter(1.4445, 0), 0)
|
||||
};
|
||||
|
||||
private static final double[] massArray = {
|
||||
0.54321, 0.43211
|
||||
};
|
||||
|
||||
private static final double[] thrustArray = {
|
||||
0.0, 0.2345678, 9999.3335, 0.0
|
||||
};
|
||||
|
||||
private static final int[] intData = {
|
||||
// Time (ms)
|
||||
0, 4, 0, 123, 412, 1445,
|
||||
// Mass specific (0.1g)
|
||||
1, 2, 5432, 4321,
|
||||
// Thrust (mN)
|
||||
5, 4, 0, 235, 9999334, 0
|
||||
};
|
||||
|
||||
|
||||
@Test
|
||||
public void testMotorDigest() throws NoSuchAlgorithmException {
|
||||
|
||||
MessageDigest correct = MessageDigest.getInstance("MD5");
|
||||
for (int value: intData) {
|
||||
correct.update((byte) ((value >>> 24) & 0xFF));
|
||||
correct.update((byte) ((value >>> 16) & 0xFF));
|
||||
correct.update((byte) ((value >>> 8) & 0xFF));
|
||||
correct.update((byte) (value & 0xFF));
|
||||
}
|
||||
|
||||
MotorDigest motor = new MotorDigest();
|
||||
motor.update(DataType.TIME_ARRAY, timeArray);
|
||||
motor.update(DataType.MASS_SPECIFIC, massArray);
|
||||
motor.update(DataType.FORCE_PER_TIME, thrustArray);
|
||||
|
||||
|
||||
assertEquals(TextUtil.hexString(correct.digest()), motor.getDigest());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCommentDigest() throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
||||
|
||||
assertEquals(md5("Hello world!"), MotorDigest.digestComment("Hello world! "));
|
||||
assertEquals(md5("Hello world!"), MotorDigest.digestComment("\nHello\tworld!\n\r"));
|
||||
assertEquals(md5("Hello world!"), MotorDigest.digestComment("Hello\r\r\r\nworld!"));
|
||||
assertEquals(md5("Hello\u00e4 world!"), MotorDigest.digestComment("Hello\u00e4\r\r\nworld!"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static String md5(String source)
|
||||
throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
return TextUtil.hexString(digest.digest(source.getBytes("UTF-8")));
|
||||
}
|
||||
}
|
@ -3,9 +3,38 @@ package net.sf.openrocket.util;
|
||||
import static java.lang.Math.PI;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class TextUtilTest {
|
||||
|
||||
@Test
|
||||
public void textHexString() {
|
||||
assertEquals("", TextUtil.hexString(new byte[0]));
|
||||
assertEquals("00", TextUtil.hexString(new byte[] { 0x00 }));
|
||||
assertEquals("ff", TextUtil.hexString(new byte[] { (byte) 0xff }));
|
||||
|
||||
for (int i=0; i <= 0xff; i++) {
|
||||
assertEquals(String.format("%02x", i), TextUtil.hexString(new byte[] { (byte) i }));
|
||||
}
|
||||
|
||||
assertEquals("0f1e2d3c4b5a6978", TextUtil.hexString(new byte[] {
|
||||
0x0f, 0x1e, 0x2d, 0x3c, 0x4b, 0x5a, 0x69, 0x78
|
||||
}));
|
||||
|
||||
Random rnd = new Random();
|
||||
for (int count=0; count<10; count++) {
|
||||
int n = rnd.nextInt(100);
|
||||
byte[] bytes = new byte[n];
|
||||
rnd.nextBytes(bytes);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b: bytes) {
|
||||
sb.append(String.format("%02x", b & 0xFF));
|
||||
}
|
||||
assertEquals(sb.toString(), TextUtil.hexString(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void specialCaseTest() {
|
||||
|
@ -26,25 +26,4 @@ public class UniqueIDTest {
|
||||
assertNotSame(id, UniqueID.uuid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hashedTest() {
|
||||
String id = UniqueID.generateHashedID();
|
||||
assertNotNull(id);
|
||||
|
||||
boolean matchhigh = false;
|
||||
boolean matchlow = false;
|
||||
for (int i=0; i<100; i++) {
|
||||
String newid = UniqueID.generateHashedID();
|
||||
assertNotNull(newid);
|
||||
assertNotSame(id, newid);
|
||||
assertTrue(newid.matches("^[0-9a-fA-F]{32}$"));
|
||||
|
||||
// Check that both high and low values occur
|
||||
matchhigh = matchhigh || newid.matches("^([0-9a-fA-F][0-9a-fA-F])*[A-F].*");
|
||||
matchlow = matchlow || newid.matches("^([0-9a-fA-F][0-9a-fA-F])*[0-4].*");
|
||||
}
|
||||
assertTrue(matchhigh);
|
||||
assertTrue(matchlow);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user