import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.TextUtil;
+/**
+ * A class that generated a "digest" of a motor. A digest is a string value that
+ * uniquely identifies a motor (like a hash code or checksum). Two motors that have
+ * the same digest behave similarly with a very high probability. The digest can
+ * therefore be used to identify motors that otherwise have the same specifications.
+ * <p>
+ * The digest only uses a limited amount of precision, so that rounding errors won't
+ * cause differing digest results.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
public class MotorDigest {
private static final double EPSILON = 0.00000000001;
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 void update(DataType type, int ... values) {
-
+ 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()+
+ throw new IllegalArgumentException("Called with type=" + type + " order=" + type.getOrder() +
" while lastOrder=" + lastOrder);
}
lastOrder = type.getOrder();
digest.update(bytes(values.length));
// Digest the values
- for (int v: values) {
+ for (int v : values) {
digest.update(bytes(v));
}
}
- private void update(DataType type, int multiplier, double ... values) {
-
+ private void update(DataType type, int multiplier, double... values) {
+
int[] intValues = new int[values.length];
- for (int i=0; i<values.length; i++) {
+ for (int i = 0; i < values.length; i++) {
double v = values[i];
v = next(v);
v *= multiplier;
update(type, intValues);
}
- public void update(DataType type, double ... values) {
+ public void update(DataType type, double... values) {
update(type, type.getMultiplier(), values);
}
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)
- };
+ (byte) ((value >>> 24) & 0xFF), (byte) ((value >>> 16) & 0xFF),
+ (byte) ((value >>> 8) & 0xFF), (byte) (value & 0xFF) };
}
-
+
/**
* Digest the contents of a thrust curve motor. The result is a string uniquely
* @return the digest
*/
public static String digestMotor(ThrustCurveMotor m) {
-
+
// Create the motor digest from data available in RASP files
MotorDigest motorDigest = new MotorDigest();
motorDigest.update(DataType.TIME_ARRAY, m.getTimePoints());
Coordinate[] cg = m.getCGPoints();
double[] cgx = new double[cg.length];
double[] mass = new double[cg.length];
- for (int i=0; i<cg.length; i++) {
+ for (int i = 0; i < cg.length; i++) {
cgx[i] = cg[i].x;
mass[i] = cg[i].weight;
}
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 encoding not supported by JRE", e);
}
-
+
return TextUtil.hexString(digest.digest());
}
--- /dev/null
+package net.sf.openrocket.preset;
+
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+
+public class ExternalComponentPreset extends RocketComponentPreset {
+
+ private final double mass;
+ private final String materialName;
+
+ public ExternalComponentPreset(Class<? extends RocketComponent> componentClass, Manufacturer manufacturer, String partName,
+ String partNo, String partDescription, double mass, String materialName) {
+ super(componentClass, manufacturer, partName, partNo, partDescription);
+
+ this.materialName = materialName;
+ this.mass = mass;
+ }
+
+
+ public String getMaterialName() {
+ return materialName;
+ }
+
+
+ public double getMass() {
+ return mass;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.preset;
+
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+
+/**
+ * A model for a preset component.
+ * <p>
+ * A preset component contains a component class type, manufacturer information,
+ * part information, and getter methods for various properties of the component.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class RocketComponentPreset {
+
+ private final Class<? extends RocketComponent> componentClass;
+ private final Manufacturer manufacturer;
+ private final String partName;
+ private final String partNo;
+ private final String partDescription;
+
+
+ public RocketComponentPreset(Class<? extends RocketComponent> componentClass, Manufacturer manufacturer,
+ String partName, String partNo, String partDescription) {
+ this.componentClass = componentClass;
+ this.manufacturer = manufacturer;
+ this.partName = partName;
+ this.partNo = partNo;
+ this.partDescription = partDescription;
+ }
+
+
+ /**
+ * Return the component class that this preset defines.
+ */
+ public Class<? extends RocketComponent> getComponentClass() {
+ return componentClass;
+ }
+
+ /**
+ * Return the manufacturer of this preset component.
+ */
+ public Manufacturer getManufacturer() {
+ return manufacturer;
+ }
+
+ /**
+ * Return the part name. This is a short, human-readable name of the part.
+ */
+ public String getPartName() {
+ return partName;
+ }
+
+ /**
+ * Return the part number. This is the part identifier (e.g. "BT-50").
+ */
+ public String getPartNo() {
+ return partNo;
+ }
+
+ /**
+ * Return the part description. This is a longer description of the component.
+ */
+ public String getPartDescription() {
+ return partDescription;
+ }
+
+}
public class ComponentChangeEvent extends ChangeEvent {
private static final long serialVersionUID = 1L;
-
+
/** A change that does not affect simulation results in any way (name, color, etc.) */
public static final int NONFUNCTIONAL_CHANGE = 1;
/** A change that affects the mass properties of the rocket */
/** A change that affects the aerodynamic properties of the rocket */
public static final int AERODYNAMIC_CHANGE = 4;
/** A change that affects the mass and aerodynamic properties of the rocket */
- public static final int BOTH_CHANGE = MASS_CHANGE|AERODYNAMIC_CHANGE; // Mass & Aerodynamic
-
+ public static final int BOTH_CHANGE = MASS_CHANGE | AERODYNAMIC_CHANGE; // Mass & Aerodynamic
+
/** A change that affects the rocket tree structure */
public static final int TREE_CHANGE = 8;
/** A change caused by undo/redo. */
public static final int UNDO_CHANGE = 16;
/** A change in the motor configurations or names */
public static final int MOTOR_CHANGE = 32;
- /** A change in the events occurring during flight. */
+ /** A change that affects the events occurring during flight. */
public static final int EVENT_CHANGE = 64;
/** A bit-field that contains all possible change types. */
private final int type;
-
+
public ComponentChangeEvent(RocketComponent component, int type) {
super(component);
if (type == 0) {
public RocketComponent getSource() {
return (RocketComponent) super.getSource();
}
-
-
+
+
public boolean isAerodynamicChange() {
return (type & AERODYNAMIC_CHANGE) != 0;
}
public boolean isMotorChange() {
return (type & MOTOR_CHANGE) != 0;
}
-
+
public int getType() {
return type;
}
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.material.Material;
+import net.sf.openrocket.material.Material.Type;
+import net.sf.openrocket.preset.ExternalComponentPreset;
+import net.sf.openrocket.preset.RocketComponentPreset;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Prefs;
if (material.equals(mat))
return;
material = mat;
+ clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
}
+ @Override
+ protected void loadFromPreset(RocketComponentPreset preset) {
+ super.loadFromPreset(preset);
+
+ ExternalComponentPreset p = (ExternalComponentPreset) preset;
+ String materialName = p.getMaterialName();
+ double mass = p.getMass();
+
+ double volume = getComponentVolume();
+ double density;
+ if (volume > 0.00001) {
+ density = mass / volume;
+ } else {
+ density = 1000;
+ }
+
+ Material mat = Material.newMaterial(Type.BULK, materialName, density, true);
+ setMaterial(mat);
+ }
+
+
@Override
protected List<RocketComponent> copyFrom(RocketComponent c) {
ExternalComponent src = (ExternalComponent) c;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.preset.RocketComponentPreset;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.BugException;
// Unique ID of the component
private String id = null;
+ // Preset component this component is based upon
+ private RocketComponentPreset presetComponent = null;
+
+
/**
* Used to invalidate the component after calling {@link #copyFrom(RocketComponent)}.
*/
+ /**
+ * Return the preset component that this component is based upon.
+ *
+ * @return the preset component, or <code>null</code> if this is not based on a preset.
+ */
+ public final RocketComponentPreset getPresetComponent() {
+ return presetComponent;
+ }
+
+ /**
+ * Set the preset component this component is based upon and load all of the
+ * preset values.
+ *
+ * @param preset the preset component to load, or <code>null</code> to clear the preset.
+ */
+ public final void loadPreset(RocketComponentPreset preset) {
+ if (presetComponent == preset) {
+ return;
+ }
+
+ if (preset == null) {
+ clearPreset();
+ return;
+ }
+
+ if (preset.getComponentClass() != this.getClass()) {
+ throw new IllegalArgumentException("Attempting to load preset of type " + preset.getComponentClass()
+ + " into component of type " + this.getClass());
+ }
+
+ RocketComponent root = getRoot();
+ final Rocket rocket;
+ if (root instanceof Rocket) {
+ rocket = (Rocket) root;
+ } else {
+ rocket = null;
+ }
+
+ try {
+ if (rocket != null) {
+ rocket.freeze();
+ }
+
+ loadFromPreset(preset);
+
+ this.presetComponent = preset;
+ fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
+
+ } finally {
+ if (rocket != null) {
+ rocket.thaw();
+ }
+ }
+ }
+
+
+ /**
+ * Load component properties from the specified preset. The preset is guaranteed
+ * to be of the correct type.
+ * <p>
+ * This method should fire the appropriate events related to the changes. The rocket
+ * is frozen by the caller, so the events will be automatically combined.
+ *
+ * @param preset the preset to load from
+ */
+ protected void loadFromPreset(RocketComponentPreset preset) {
+ // No-op
+ }
+
+
+ /**
+ * Clear the current component preset. This does not affect the component properties
+ * otherwise.
+ */
+ public final void clearPreset() {
+ if (presetComponent == null)
+ return;
+ presetComponent = null;
+ fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
+ }
+
+
+
/**
* Returns the unique ID of the component.
*