1 package net.sf.openrocket.preset.loader;
3 import au.com.bytecode.opencsv.CSVReader;
4 import net.sf.openrocket.database.Databases;
5 import net.sf.openrocket.file.preset.ColumnDefinition;
6 import net.sf.openrocket.file.rocksim.RocksimNoseConeCode;
7 import net.sf.openrocket.gui.print.PrintUnit;
8 import net.sf.openrocket.gui.util.SwingPreferences;
9 import net.sf.openrocket.material.Material;
10 import net.sf.openrocket.preset.ComponentPreset;
11 import net.sf.openrocket.preset.ComponentPresetFactory;
12 import net.sf.openrocket.preset.InvalidComponentPresetException;
13 import net.sf.openrocket.preset.TypedKey;
14 import net.sf.openrocket.preset.TypedPropertyMap;
15 import net.sf.openrocket.startup.Application;
16 import net.sf.openrocket.unit.UnitGroup;
17 import net.sf.openrocket.util.ArrayList;
18 import net.sf.openrocket.util.BugException;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
34 * Primary entry point for parsing component CSV files that are in Rocksim format.
36 public class RocksimComponentFileLoader {
39 * Common unit of measure key. Rocksim format allows different types of units.
41 public final static TypedKey<String> UNITS_OF_MEASURE = new TypedKey<String>("Units", String.class);
44 public static List<String[]> load(RocksimComponentFileType type) {
45 return load(RocksimComponentFileLoader.class.getResourceAsStream("/performancerocketry/" + type.getDefaultFileName()));
48 public static List<String[]> load(File file) throws FileNotFoundException {
49 return load(new FileInputStream(file));
52 public static List<String[]> load(InputStream is) {
54 return new ArrayList<String[]>();
56 InputStreamReader r = null;
58 r = new InputStreamReader(is);
60 // Create the CSV reader. Use comma separator.
61 CSVReader reader = new CSVReader(r, ',', '\'', '\\');
63 //Read and throw away the header row.
66 //Read the rest of the file as data rows.
67 return reader.readAll();
69 catch (IOException e) {
76 catch (IOException e) {
81 return new ArrayList<String[]>();
85 * Rocksim CSV units are either inches or mm. A value of 0 or "in." indicate inches. A value of 1 or "mm" indicate
88 * @param units the value from the file
89 * @return true if it's inches
91 private static boolean isInches(String units) {
92 String tmp = units.trim().toLowerCase();
93 return "0".equals(tmp) || tmp.startsWith("in");
97 * Convert inches or millimeters to meters.
99 * @param units a Rocksim CSV string representing the kind of units.
100 * @param value the original value within the CSV file
101 * @return the value in meters
103 private static double convertLength(String units, double value) {
104 if (isInches(units)) {
105 return PrintUnit.INCHES.toMeters(value);
108 return PrintUnit.MILLIMETERS.toMeters(value);
113 * Remove all occurrences of the given character. Note: this is done because some manufacturers embed double
114 * quotes in their descriptions or material names. Those are stripped away because they cause all sorts of
115 * matching/lookup issues.
117 * @param target the target string to be operated upon
118 * @param toBeRemoved the character to remove
119 * @return target, minus every occurrence of toBeRemoved
121 private static String stripAll(String target, Character toBeRemoved) {
122 StringBuilder sb = new StringBuilder();
123 for (int i = 0; i < target.length(); i++) {
124 Character c = target.charAt(i);
125 if (!c.equals(toBeRemoved)) {
129 return sb.toString();
133 * Convert all words in a given string to Camel Case (first letter capitalized). Words are assumed to be
134 * separated by a space. Note: this is done because some manufacturers define their material name in Camel Case
135 * but the component part references the material in lower case. That causes matching/lookup issues that's
136 * easiest handled this way (rather than converting everything to lower case.
138 * @param target the target string to be operated upon
139 * @return target, with the first letter of each word in uppercase
141 private static String toCamelCase(String target) {
142 StringBuilder sb = new StringBuilder();
143 String[] t = target.split("[ ]");
144 if (t != null && t.length > 0) {
145 for (int i = 0; i < t.length; i++) {
147 s = s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
148 sb.append(s).append(" ");
150 return sb.toString().trim();
157 private static Collection<ComponentPreset> commonLoader(final List<String[]> theData,
158 final List<TypedKey<?>> keyMap,
159 final Map<String, Material> materialMap,
160 final ComponentPreset.Type type) {
161 Collection<ComponentPreset> result = new ArrayList<ComponentPreset>();
162 List<TypedPropertyMap> templates = new java.util.ArrayList<TypedPropertyMap>();
163 Set<String> favorites = Application.getPreferences().getComponentFavorites();
166 ColumnDefinition[] columns = new ColumnDefinition[keyMap.size()];
167 for (int i = 0; i < keyMap.size(); i++) {
168 TypedKey key = keyMap.get(i);
170 columns[i] = new ColumnDefinition(key);
172 if (key.getName().equals("Units")) {
177 for (int i = 0; i < theData.size(); i++) {
178 String[] item = theData.get(i);
179 TypedPropertyMap preset = new TypedPropertyMap();
181 for (int j = 0; j < columns.length; j++) {
182 if (j < item.length) {
183 String value = item[j];
187 value = value.trim();
188 value = stripAll(value, '"');
189 if (value.length() == 0) {
192 final TypedKey typedKey = columns[j].getKey();
193 if (typedKey.equals(ComponentPreset.MATERIAL)) {
194 preset.put(ComponentPreset.MATERIAL, materialMap.get(value));
196 else if (typedKey.equals(ComponentPreset.SHAPE)) {
197 preset.put(ComponentPreset.SHAPE, RocksimNoseConeCode.fromShapeNameOrCode(value).asOpenRocket());
200 final UnitGroup unitGroup = typedKey.getUnitGroup();
201 if (unitGroup != null && unitGroup.equals(UnitGroup.UNITS_LENGTH)) {
202 columns[j].setProperty(preset, convertLength(item[uom], Double.valueOf(value)));
205 columns[j].setProperty(preset, value);
210 preset.put(ComponentPreset.TYPE, type);
211 templates.add(preset);
214 for (TypedPropertyMap o : templates) {
216 ComponentPreset preset = ComponentPresetFactory.create(o);
217 if (favorites.contains(preset.preferenceKey())) {
218 preset.setFavorite(true);
222 catch (InvalidComponentPresetException ex) {
223 throw new BugException(ex);
230 static class BodyTubeLoader {
231 private final static int MFG_INDEX = 0;
232 private final static int PART_NO_INDEX = 1;
233 private final static int DESCRIPTION_INDEX = 2;
234 private final static int UNITS_INDEX = 3;
235 private final static int ID_INDEX = 4;
236 private final static int OD_INDEX = 5;
237 private final static int LENGTH_INDEX = 6;
238 private final static int MATERIAL_INDEX = 7;
240 public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(8);
243 keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
244 keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
245 keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
246 keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
247 keyMap.add(ID_INDEX, ComponentPreset.INNER_DIAMETER);
248 keyMap.add(OD_INDEX, ComponentPreset.OUTER_DIAMETER);
249 keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
250 keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
253 public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
254 List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.BODY_TUBE);
255 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BODY_TUBE);
258 public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
259 FileNotFoundException {
260 List<String[]> data = RocksimComponentFileLoader.load(file);
261 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BODY_TUBE);
267 * Tube coupler parser. Although there are additional fields in the file, they are not used by
268 * most (any?) manufacturers so we ignore them entirely.
270 static class TubeCouplerLoader extends BodyTubeLoader {
271 public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
272 List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.TUBE_COUPLER);
273 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TUBE_COUPLER);
276 public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
277 FileNotFoundException {
278 List<String[]> data = RocksimComponentFileLoader.load(file);
279 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TUBE_COUPLER);
284 * Engine block parser. Although there are additional fields in the file, they are not used by
285 * most (any?) manufacturers so we ignore them entirely.
287 static class EngineBlockLoader extends BodyTubeLoader {
288 public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
289 List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.ENGINE_BLOCK);
290 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.ENGINE_BLOCK);
293 public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
294 FileNotFoundException {
295 List<String[]> data = RocksimComponentFileLoader.load(file);
296 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.ENGINE_BLOCK);
301 static class BulkheadLoader extends BodyTubeLoader {
302 public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
303 List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.BULKHEAD);
304 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BULK_HEAD);
307 public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
308 FileNotFoundException {
309 List<String[]> data = RocksimComponentFileLoader.load(file);
310 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.BULK_HEAD);
314 static class CenteringRingLoader extends BodyTubeLoader {
315 public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
316 List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.CENTERING_RING);
317 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.CENTERING_RING);
320 public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
321 FileNotFoundException {
322 List<String[]> data = RocksimComponentFileLoader.load(file);
323 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.CENTERING_RING);
327 static class NoseConeLoader {
328 public static final int MFG_INDEX = 0;
329 public static final int PART_NO_INDEX = 1;
330 public static final int DESCRIPTION_INDEX = 2;
331 public static final int UNITS_INDEX = 3;
332 public static final int LENGTH_INDEX = 4;
333 public static final int OUTER_DIA_INDEX = 5;
334 public static final int LD_RATIO_INDEX = 6;
335 public static final int INSERT_LENGTH_INDEX = 7;
336 public static final int INSERT_OD_INDEX = 8;
337 public static final int THICKNESS_INDEX = 9;
338 public static final int SHAPE_INDEX = 10;
339 public static final int CONFIG_INDEX = 11;
340 public static final int MATERIAL_INDEX = 12;
341 public static final int CG_LOC_INDEX = 13;
342 public static final int MASS_UNITS_INDEX = 14;
343 public static final int MASS_INDEX = 15;
344 public static final int BASE_EXT_LEN_INDEX = 16;
346 public final static TypedKey<Double> LD_RATIO = new TypedKey<Double>("Len/Dia Ratio", Double.class);
347 public final static TypedKey<Double> BASE_EXT_LEN = new TypedKey<Double>("Base Ext Len", Double.class, UnitGroup.UNITS_LENGTH);
348 public final static TypedKey<String> CONFIG = new TypedKey<String>("Config", String.class);
349 public final static TypedKey<Double> CG_LOC = new TypedKey<Double>("CG Loc", Double.class, UnitGroup.UNITS_LENGTH);
350 public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(17);
353 keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
354 keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
355 keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
356 keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
357 keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
358 keyMap.add(OUTER_DIA_INDEX, ComponentPreset.OUTER_DIAMETER);
359 keyMap.add(LD_RATIO_INDEX, LD_RATIO);
360 keyMap.add(INSERT_LENGTH_INDEX, ComponentPreset.SHOULDER_LENGTH);
361 keyMap.add(INSERT_OD_INDEX, ComponentPreset.SHOULDER_DIAMETER);
362 keyMap.add(THICKNESS_INDEX, ComponentPreset.THICKNESS);
363 keyMap.add(SHAPE_INDEX, ComponentPreset.SHAPE);
364 keyMap.add(CONFIG_INDEX, CONFIG);
365 keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
366 keyMap.add(CG_LOC_INDEX, CG_LOC);
367 keyMap.add(MASS_UNITS_INDEX, UNITS_OF_MEASURE);
368 keyMap.add(MASS_INDEX, ComponentPreset.MASS);
369 keyMap.add(BASE_EXT_LEN_INDEX, BASE_EXT_LEN);
372 public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
373 List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.NOSE_CONE);
374 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.NOSE_CONE);
377 public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
378 FileNotFoundException {
379 List<String[]> data = RocksimComponentFileLoader.load(file);
380 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.NOSE_CONE);
384 static class TransitionLoader {
385 public static final int MFG_INDEX = 0;
386 public static final int PART_NO_INDEX = 1;
387 public static final int DESCRIPTION_INDEX = 2;
388 public static final int UNITS_INDEX = 3;
389 public static final int FRONT_INSERT_LENGTH_INDEX = 4;
390 public static final int FRONT_INSERT_OD_INDEX = 5;
391 public static final int FRONT_OD_INDEX = 6;
392 public static final int LENGTH_INDEX = 7;
393 public static final int REAR_OD_INDEX = 8;
394 public static final int CORE_DIA_INDEX = 9;
395 public static final int REAR_INSERT_LENGTH_INDEX = 10;
396 public static final int REAR_INSERT_OD_INDEX = 11;
397 public static final int THICKNESS_INDEX = 12;
398 public static final int CONFIG_INDEX = 13;
399 public static final int MATERIAL_INDEX = 14;
400 public static final int CG_LOC_INDEX = 15;
401 public static final int MASS_UNITS_INDEX = 16;
402 public static final int MASS_INDEX = 17;
403 public static final int SHAPE_INDEX = 18;
405 public final static TypedKey<String> CONFIG = new TypedKey<String>("Config", String.class);
406 public final static TypedKey<String> IGNORE = new TypedKey<String>("Ignore", String.class);
407 public final static TypedKey<Double> CG_LOC = new TypedKey<Double>("CG Loc", Double.class, UnitGroup.UNITS_LENGTH);
408 public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(19);
411 keyMap.add(MFG_INDEX, ComponentPreset.MANUFACTURER);
412 keyMap.add(PART_NO_INDEX, ComponentPreset.PARTNO);
413 keyMap.add(DESCRIPTION_INDEX, ComponentPreset.DESCRIPTION);
414 keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
415 keyMap.add(FRONT_INSERT_LENGTH_INDEX, ComponentPreset.FORE_SHOULDER_LENGTH);
416 keyMap.add(FRONT_INSERT_OD_INDEX, ComponentPreset.FORE_SHOULDER_DIAMETER);
417 keyMap.add(FRONT_OD_INDEX, ComponentPreset.FORE_OUTER_DIAMETER);
418 keyMap.add(LENGTH_INDEX, ComponentPreset.LENGTH);
419 keyMap.add(REAR_OD_INDEX, ComponentPreset.OUTER_DIAMETER);
420 keyMap.add(CORE_DIA_INDEX, IGNORE);
421 keyMap.add(REAR_INSERT_LENGTH_INDEX, ComponentPreset.SHOULDER_LENGTH);
422 keyMap.add(REAR_INSERT_OD_INDEX, ComponentPreset.SHOULDER_DIAMETER);
423 keyMap.add(THICKNESS_INDEX, ComponentPreset.THICKNESS);
424 keyMap.add(CONFIG_INDEX, CONFIG);
425 keyMap.add(MATERIAL_INDEX, ComponentPreset.MATERIAL);
426 keyMap.add(CG_LOC_INDEX, CG_LOC);
427 keyMap.add(MASS_UNITS_INDEX, UNITS_OF_MEASURE);
428 keyMap.add(MASS_INDEX, ComponentPreset.MASS);
429 keyMap.add(SHAPE_INDEX, ComponentPreset.SHAPE);
432 public Collection<ComponentPreset> load(Map<String, Material> materialMap) {
433 List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.TRANSITION);
434 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TRANSITION);
437 public Collection<ComponentPreset> load(Map<String, Material> materialMap, File file) throws
438 FileNotFoundException {
439 List<String[]> data = RocksimComponentFileLoader.load(file);
440 return commonLoader(data, keyMap, materialMap, ComponentPreset.Type.TRANSITION);
444 static class MaterialLoader {
445 private final static int MATERIAL_INDEX = 0;
446 private final static int UNITS_INDEX = 1;
447 private final static int DENSITY_INDEX = 2;
448 private final static int LOW_INDEX = 3;
449 private final static int HIGH_INDEX = 4;
450 private final static int CLASS_INDEX = 5;
451 private final static int ROCKETRY_USE_INDEX = 6;
452 private final static int BODY_TUBES_INDEX = 7;
453 public static final int FIN_SETS_INDEX = 8;
454 public static final int LAUNCH_LUGS_INDEX = 9;
455 public static final int CORDS_INDEX = 10;
456 public static final int NOSE_INDEX = 11;
457 public static final int PARACHUTE_INDEX = 12;
458 public static final int STREAMER_INDEX = 13;
459 public static final int TRANSITION_INDEX = 14;
460 public static final int RING_INDEX = 15;
461 public static final int BULKHEAD_INDEX = 16;
462 public static final int ENGINE_BLOCK_INDEX = 17;
463 public static final int SLEEVE_INDEX = 18;
464 public static final int TUBE_COUPLER_INDEX = 19;
465 public static final int KNOWN_DIM_TYPE_INDEX = 27;
466 public static final int KNOWN_DIM_UNITS_INDEX = 28;
467 public static final int KNOWN_DIM_VALUE_INDEX = 29;
469 public final static List<TypedKey<?>> keyMap = new ArrayList<TypedKey<?>>(8);
470 public final static TypedKey<String> MATERIAL_NAME = new TypedKey<String>("Material Name", String.class);
471 public final static TypedKey<Double> DENSITY = new TypedKey<Double>("Density", Double.class);
473 static class MaterialAdapter {
475 double conversionFactor;
477 MaterialAdapter(Material.Type theType, double cf) {
479 conversionFactor = cf;
483 private final static Map<String, MaterialAdapter> materialAdapterMap = new HashMap<String, MaterialAdapter>();
486 materialAdapterMap.put("g/cm", new MaterialAdapter(Material.Type.LINE, 0.1d));
487 materialAdapterMap.put("g/cm2", new MaterialAdapter(Material.Type.SURFACE, 10.0d));
488 materialAdapterMap.put("g/cm3", new MaterialAdapter(Material.Type.BULK, 1000.0d));
489 materialAdapterMap.put("kg/m3", new MaterialAdapter(Material.Type.BULK, 1d));
490 materialAdapterMap.put("lb/ft3", new MaterialAdapter(Material.Type.BULK, 16.0184634d));
491 materialAdapterMap.put("oz/in", new MaterialAdapter(Material.Type.LINE, 1.11612296d));
492 materialAdapterMap.put("oz/in2", new MaterialAdapter(Material.Type.SURFACE, 43.9418487));
494 keyMap.add(MATERIAL_INDEX, MATERIAL_NAME);
495 keyMap.add(UNITS_INDEX, UNITS_OF_MEASURE);
496 keyMap.add(DENSITY_INDEX, DENSITY);
499 static Map<String, Material> load() {
500 List<String[]> data = RocksimComponentFileLoader.load(RocksimComponentFileType.MATERIAL);
501 Map<String, Material> materialMap = new HashMap<String, Material>();
503 for (int i = 0; i < data.size(); i++) {
505 String[] strings = data.get(i);
506 MaterialAdapter ma = materialAdapterMap.get(strings[UNITS_INDEX]);
507 double metricDensity = ma.conversionFactor * Double.parseDouble(strings[DENSITY_INDEX]);
508 final String cleanedMaterialName = stripAll(strings[MATERIAL_INDEX], '"').trim();
509 final Material material = Databases.findMaterial(ma.type, cleanedMaterialName,
510 metricDensity, true);
511 materialMap.put(cleanedMaterialName, material);
512 materialMap.put(cleanedMaterialName.toLowerCase(), material);
513 materialMap.put(toCamelCase(cleanedMaterialName), material);
515 catch (Exception e) {
516 //Trap a bad row and move on
517 //TODO: log it? Display to user?
524 public static void main(String[] args) {
525 Application.setPreferences(new SwingPreferences());
526 Map<String, Material> materialMap = MaterialLoader.load();
527 Collection<ComponentPreset> presetNC = new NoseConeLoader().load(materialMap);
528 Collection<ComponentPreset> presetBC = new BodyTubeLoader().load(materialMap);
529 Collection<ComponentPreset> presetBH = new BulkheadLoader().load(materialMap);
530 Collection<ComponentPreset> presetCR = new CenteringRingLoader().load(materialMap);
531 Collection<ComponentPreset> presetTC = new TubeCouplerLoader().load(materialMap);
532 Collection<ComponentPreset> presetTR = new TransitionLoader().load(materialMap);
533 Collection<ComponentPreset> presetEB = new EngineBlockLoader().load(materialMap);
535 for (Iterator<ComponentPreset> iterator = presetNC.iterator(); iterator.hasNext(); ) {
536 ComponentPreset next = iterator.next();
537 System.err.println(next);
539 for (Iterator<ComponentPreset> iterator = presetBC.iterator(); iterator.hasNext(); ) {
540 ComponentPreset next = iterator.next();
541 System.err.println(next);
543 for (Iterator<ComponentPreset> iterator = presetBH.iterator(); iterator.hasNext(); ) {
544 ComponentPreset next = iterator.next();
545 System.err.println(next);
547 for (Iterator<ComponentPreset> iterator = presetCR.iterator(); iterator.hasNext(); ) {
548 ComponentPreset next = iterator.next();
549 System.err.println(next);
551 for (Iterator<ComponentPreset> iterator = presetTC.iterator(); iterator.hasNext(); ) {
552 ComponentPreset next = iterator.next();
553 System.err.println(next);
555 for (Iterator<ComponentPreset> iterator = presetTR.iterator(); iterator.hasNext(); ) {
556 ComponentPreset next = iterator.next();
557 System.err.println(next);
559 for (Iterator<ComponentPreset> iterator = presetEB.iterator(); iterator.hasNext(); ) {
560 ComponentPreset next = iterator.next();
561 System.err.println(next);
567 //The oddities I've found thus far in the stock Rocksim data:
568 //1. BTDATA.CSV - Totally Tubular goofed up their part no. and description columns (They messed up TCDATA also)
569 //2. NCDATA.CSV - Estes Balsa nose cones are classified as G10 Fiberglass
570 //3. TRDATA.CSV - Apogee Saturn LEM Transition has no part number; Balsa Machining transitions have blank diameter