updates for 0.9.4
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Tue, 24 Nov 2009 19:56:40 +0000 (19:56 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Tue, 24 Nov 2009 19:56:40 +0000 (19:56 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@28 180e2498-e6e9-4542-8430-84ac67f01cd8

49 files changed:
.classpath
ChangeLog
ReleaseNotes
TODO
build.properties
datafiles/thrustcurves/SF_A8.eng
datafiles/thrustcurves/SF_B4.eng
datafiles/thrustcurves/SF_C2.eng
datafiles/thrustcurves/SF_C6.eng
datafiles/thrustcurves/SF_D7.eng
dists/OpenRocket-0.9.4-src.zip [new file with mode: 0644]
dists/OpenRocket-0.9.4.jar [new file with mode: 0644]
html/actions/.htaccess
html/actions/reportbug.php
html/actions/updates.php [new file with mode: 0644]
src/net/sf/openrocket/database/Databases.java
src/net/sf/openrocket/file/RASPMotorLoader.java
src/net/sf/openrocket/file/RockSimMotorLoader.java
src/net/sf/openrocket/gui/adaptors/DoubleModel.java
src/net/sf/openrocket/gui/adaptors/EnumModel.java
src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java
src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java
src/net/sf/openrocket/gui/dialogs/BugReportDialog.java
src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java
src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
src/net/sf/openrocket/gui/main/BasicFrame.java
src/net/sf/openrocket/gui/main/ExceptionHandler.java
src/net/sf/openrocket/motor/Motor.java
src/net/sf/openrocket/motor/MotorDigest.java [new file with mode: 0644]
src/net/sf/openrocket/motor/ThrustCurveMotor.java
src/net/sf/openrocket/rocketcomponent/BodyTube.java
src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java
src/net/sf/openrocket/rocketcomponent/InnerTube.java
src/net/sf/openrocket/rocketcomponent/MotorMount.java
src/net/sf/openrocket/rocketcomponent/Rocket.java
src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
src/net/sf/openrocket/rocketcomponent/Transition.java
src/net/sf/openrocket/unit/FrequencyUnit.java [new file with mode: 0644]
src/net/sf/openrocket/unit/UnitGroup.java
src/net/sf/openrocket/unit/Value.java
src/net/sf/openrocket/unit/ValueComparator.java [new file with mode: 0644]
src/net/sf/openrocket/util/Base64.java
src/net/sf/openrocket/util/Prefs.java
src/net/sf/openrocket/util/TextUtil.java
src/net/sf/openrocket/util/UniqueID.java
src/net/sf/openrocket/utils/MotorPrinter.java
test/net/sf/openrocket/motor/MotorDigestTest.java [new file with mode: 0644]
test/net/sf/openrocket/util/TextUtilTest.java
test/net/sf/openrocket/util/UniqueIDTest.java

index a98ed5b2fcfc8b840a29f460e4eab8f0710277f3..b97a4aa3844cbd476d7e3e03e80e478ba79727ce 100644 (file)
@@ -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>
index 91949f53218d4314625eadb0774ff8e34d9b7569..5967b47b0ed1e90f1731a882eb8d3d01f1325ec3 100644 (file)
--- a/ChangeLog
+++ b/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
index 0120d50097c12029a9b0ec10b605fd721bfc0ecc..698f5ca4f9f33bbf4ba244008f56b3bb204749db 100644 (file)
@@ -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):
 -------------------------------
 
diff --git a/TODO b/TODO
index 9fff6762e5b88f848a5a30a3463caf02fe8089f8..67f030c71ef623f389ff98119aad15ab555458f1 100644 (file)
--- a/TODO
+++ b/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
 
index 16dafdf9e4bc90c55d2627987223e781b8da1328..14052b6bd14f402bcb5c27cf4afd8178b04b2d40 100644 (file)
@@ -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
index 6c9c9081b00e83ec24a31038be1e69dfd74a6e53..292f5e34ec5c42cf8047588fab372b00a1417091 100644 (file)
@@ -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
index 1a0db346435c31a20d7d055e8a5fd9551f71d5ed..e8a4adf883e331bde5578e4c031e8e70f9450fc6 100644 (file)
@@ -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
index 4698c6dd1012aec0831ff3747fd9f1020874cf93..8e0b6e6eb65ee360cf1306d0c814b6f7cad6698e 100644 (file)
@@ -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
index 32394516850a4bbc26b103c94f891297ff061510..00da30e32df3f90332b6e5c7d6f9d93087b1620b 100644 (file)
@@ -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
index 9b424fb810d50177f8663c0275735d50d68ea1a7..1a7dfe024f25b52a69fba0b5e2f51db584fb6af5 100644 (file)
@@ -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
diff --git a/dists/OpenRocket-0.9.4-src.zip b/dists/OpenRocket-0.9.4-src.zip
new file mode 100644 (file)
index 0000000..7643ff4
Binary files /dev/null and b/dists/OpenRocket-0.9.4-src.zip differ
diff --git a/dists/OpenRocket-0.9.4.jar b/dists/OpenRocket-0.9.4.jar
new file mode 100644 (file)
index 0000000..6220f71
Binary files /dev/null and b/dists/OpenRocket-0.9.4.jar differ
index 6777c7e834c65050baac1c3372e655c29b886586..d18e09c2e7d4b685dc358000e70a0826bb98ebc0 100644 (file)
@@ -8,3 +8,7 @@
 
 Redirect 307 /actions/reportbug http://sampo.kapsi.fi/openrocket/reportbug.php
 
+RewriteEngine On
+RewriteBase /actions/
+RewriteRule ^updates$ updates.php
+
index a6864a28ce2877ad04e5a3aec75c4b1ed3a99a5f..86b8d8cf28c31dad538626ad8ae83a2ed7c2b922 100644 (file)
@@ -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")) {
        
diff --git a/html/actions/updates.php b/html/actions/updates.php
new file mode 100644 (file)
index 0000000..e498145
--- /dev/null
@@ -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");
+
+?>
\ No newline at end of file
index d5d7f465147c5da8acb444b778529ed68783523c..cdf39c2e41ab6897e62accbb2728f28d449f1d8a 100644 (file)
@@ -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));
index 558e7cd8e25d1027cc75314c67ee4cc472af16f6..409d2188e68faa0417a661ccb7e462eeaeef9a27 100644 (file)
@@ -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) {
                        
index b39a9218e4bdfef30f16b63e76e28f3253813419..48a15906b599be08cd6702acfcd4ed376cc9291c 100644 (file)
@@ -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;
+       }
 }
index b39cb0104de62fb78ec5071aceb8358f65aedcdb..da0c7747b2e48aec26968f2f33501629cd08e37f 100644 (file)
@@ -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);
                }
        }
 
index f6e7d679a31cb4c6c4818618d5a9b22fdbdc4d90..0e141a65ab9b5f5227f7770d5cd6662afe5d2b23 100644 (file)
@@ -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
index 52e02686afb00ae360f7e2d9d8e9e008d4bbb967..369b09b6b73b0251199bf0e5044b22f4c2d9a0a1 100644 (file)
@@ -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) {
                        }
                }
-               
-               
        }
 }
index 427868359d760f30708f8838d0631f4896480329..691fcf7f1d0d228c37da816d38cef59954655e5b 100644 (file)
@@ -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);
        }
index 9ebd9f2a8f640bdda9e539db33e7885cfe762ae6..b5b44b103c88766874519400a6db0b309d9dc5dd 100644 (file)
@@ -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);
        }
        
index 887085df94145b341cb83f26e9484fa872ddc494..0aba355f902ee7348de5e9f2643055f5de1ef908 100644 (file)
@@ -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);
-               }
-       }
-       
 }
index e5f38260d9f4e66a90c37b89dfe68d0fc89b9996..f2ed0ef2ab2579d0f2ae5cc76e7a01438c16c4dd 100644 (file)
@@ -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
index f7777a0025c03cc306ef39355e832342deacdd77..c2d0d3367a62b866572745a181a118bf921a82e5 100644 (file)
@@ -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,
index 7b233b022b764c03d0771145411b169f76d781ab..c1529a3c122d34665e4137928a831409708827d0 100644 (file)
@@ -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) + "...";
                }
                
                
index 0da9c3b42e216fcef9c2eab775f3614c5d1c4d6e..ce746d332ab24b434557bd1f28f8fb14ffdb0c77 100644 (file)
@@ -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
diff --git a/src/net/sf/openrocket/motor/MotorDigest.java b/src/net/sf/openrocket/motor/MotorDigest.java
new file mode 100644 (file)
index 0000000..c6ec939
--- /dev/null
@@ -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());
+       }
+       
+}
index e495defeb2ff50a833c8cd508307cb5e6f220a04..1ccfec20cfaae8d12afbeb672a7f9e3038f381cc 100644 (file)
@@ -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();
        }
-       
+
 }
index d3368f1f1365e97eed4e8032db0dc57974631589..05ee9eed92ec9e0aa8b77137fd1343f955c2b70c 100644 (file)
@@ -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)))
index d5275db88857c1072153fb7f326fa3047e0a87c6..05862f3d4384e98593927d20f0d4ba4702411b6c 100644 (file)
@@ -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.
index 901b5cb60fe067fa910bc60919cc1ee918731295..0c30042b09d087562cc78364eb8d17bfdf78e69f 100644 (file)
@@ -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();
index 20f3c82e9e2a1b5305733ab719e5e20f5341987f..6735ee742ddba78fd05e57032f4bcc64721efcc2 100644 (file)
@@ -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();
        
        
index 145e4cf0456d947ac6471031bc245b91d25b7b52..9ab110b49c75996e2a5efb337638d980a0cf43ef 100644 (file)
@@ -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
index ed0bffb6a3ce85f8e31560aaa018fbbea5605e11..e25ba691d939e143ecaeb39d9c66e15f91a35f67 100644 (file)
@@ -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());
                }
        }
        
index 5478545fe00f0cc74168b2a4aa1ba980c0b3fa28..f598ad1eeb6c5500a8be3305990f5c78245dbf44 100644 (file)
@@ -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;
diff --git a/src/net/sf/openrocket/unit/FrequencyUnit.java b/src/net/sf/openrocket/unit/FrequencyUnit.java
new file mode 100644 (file)
index 0000000..7b5d15c
--- /dev/null
@@ -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;
+       }
+
+}
index ffe80f226547b3a0ee57078be52b5fa0251a5523..1d8848b27376afe7f1a6ffcf2dfdf8dfab9eb059 100644 (file)
@@ -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);
index 54e192790c13c7c6a5f456d02afdd629f9bf5acc..5b518673fc602eeacbfc0dcc3ff23bd9d2e7c47b 100644 (file)
@@ -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());
+       }
 
        
        /**
diff --git a/src/net/sf/openrocket/unit/ValueComparator.java b/src/net/sf/openrocket/unit/ValueComparator.java
new file mode 100644 (file)
index 0000000..a948e9d
--- /dev/null
@@ -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);
+       }
+
+}
index fb8490a799b5052abc3253310455cb94ca3c8670..042d36715d41840522a586db27f4011055a18873 100644 (file)
@@ -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 {
index dc3e0db405bdc31a07ba6d006010e493e00d9cc1..70a3e558e6542ed022b899f35e41864c5d4e8c61 100644 (file)
@@ -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;
index f554dc73ba0fbf4111928ca4fbc5280c2f97d670..c6a1bb835c6e99c09983e4f60d0347bd58864c9b 100644 (file)
@@ -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).
index e7b56d4ac7dbef057d29ccfaa7b59c6a69c369ce..1cc082e816bf4ba1aaca592afe6703e76255856c 100644 (file)
@@ -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;
-       }
-       
 }
index 194e15aff75ee49a7b406aca38c417613eb8a664..ed6bdb7e2708afe47c30abf6bb110ecca924adca 100644 (file)
@@ -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;
diff --git a/test/net/sf/openrocket/motor/MotorDigestTest.java b/test/net/sf/openrocket/motor/MotorDigestTest.java
new file mode 100644 (file)
index 0000000..916ab12
--- /dev/null
@@ -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")));
+       }
+}
index 73930cf879c478c3e958b8ce4452ef06d50ab074..3b7385f00aa5d9690cb40044e1a15d589e893f94 100644 (file)
@@ -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() {
index eeec615337092e0e5ce375ab1799ac989bbf0199..c77da324e63ed0e47aad2a1a6abbeff275b3bf8a 100644 (file)
@@ -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);
-       }
-       
 }