From 05d1b91d90a0715da3b4f7ac786821ae1fbd6d8f Mon Sep 17 00:00:00 2001 From: Kevin Ruland Date: Thu, 19 Jul 2012 21:35:52 +0000 Subject: [PATCH] Sizable commit with lots of changes to support decals in openrocket files. Added support for zip container support. Changed Decal to hold a String instead of URL. Refactored GeneralRocketLoader out of the RocketLoader hierarchy so the GeneralRocketLoader knows about files and RocketLoader only knows about streams. Changed the OpenRocket file format to 1.6 and added and it's children for ambient light and decals. Implemented a DecalRegistry which is a member of the OpenRocketDocument. This class is used to get InputStreams for decals by their names. --- .../openrocket/startup/TextureOutputTest.java | 59 +++ .../sf/openrocket/startup/TextureTest.java | 17 +- .../startup/al1 Apocalypse_54mmtestFr.rkt.xml | 16 +- core/resources/l10n/messages.properties | 2 + .../appearance/AppearanceBuilder.java | 10 +- .../net/sf/openrocket/appearance/Decal.java | 10 +- .../sf/openrocket/document/DecalRegistry.java | 54 +++ .../document/OpenRocketDocument.java | 12 +- .../openrocket/file/AbstractRocketLoader.java | 26 -- .../openrocket/file/GeneralRocketLoader.java | 61 ++- .../net/sf/openrocket/file/RocketLoader.java | 3 - .../net/sf/openrocket/file/RocketSaver.java | 8 +- .../file/openrocket/OpenRocketSaver.java | 356 ++++++++++++------ .../openrocket/importt/OpenRocketLoader.java | 100 ++++- .../savers/RocketComponentSaver.java | 115 ++++-- .../file/rocksim/export/RocksimSaver.java | 19 +- .../importt/RockSimAppearanceBuilder.java | 19 +- .../gui/configdialog/AppearancePanel.java | 17 +- .../gui/figure3d/FigureRenderStrategy.java | 4 + .../gui/figure3d/RealisticRenderStrategy.java | 59 +-- .../gui/figure3d/RenderStrategy.java | 8 + .../gui/figure3d/RocketFigure3d.java | 7 +- .../sf/openrocket/gui/main/BasicFrame.java | 2 +- .../gui/scalefigure/RocketPanel.java | 2 +- .../openrocket/gui/util/OpenFileWorker.java | 12 +- .../openrocket/gui/util/SaveFileWorker.java | 4 +- .../net/sf/openrocket/IntegrationTest.java | 9 +- 27 files changed, 720 insertions(+), 291 deletions(-) create mode 100644 core/3d-Test-Junk/net/sf/openrocket/startup/TextureOutputTest.java create mode 100644 core/src/net/sf/openrocket/document/DecalRegistry.java diff --git a/core/3d-Test-Junk/net/sf/openrocket/startup/TextureOutputTest.java b/core/3d-Test-Junk/net/sf/openrocket/startup/TextureOutputTest.java new file mode 100644 index 000000000..bbe525862 --- /dev/null +++ b/core/3d-Test-Junk/net/sf/openrocket/startup/TextureOutputTest.java @@ -0,0 +1,59 @@ +package net.sf.openrocket.startup; +import java.io.File; + +import net.sf.openrocket.database.ComponentPresetDatabase; +import net.sf.openrocket.database.ThrustCurveMotorSetDatabase; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.StorageOptions; +import net.sf.openrocket.file.DatabaseMotorFinder; +import net.sf.openrocket.file.GeneralRocketLoader; +import net.sf.openrocket.file.openrocket.OpenRocketSaver; +import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.l10n.ResourceBundleTranslator; + +/** + * An application for quickly testing 3d figure witout all the OpenRocket user + * interface + * + * @author bkuker + * + */ +public class TextureOutputTest { + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + Application.setBaseTranslator(new ResourceBundleTranslator("l10n.messages")); + Application.setMotorSetDatabase(new ThrustCurveMotorSetDatabase(false) { + { + startLoading(); + } + + @Override + protected void loadMotors() { + } + }); + Application.setPreferences(new SwingPreferences()); + + // Must be done after localization is initialized + ComponentPresetDatabase componentPresetDao = new ComponentPresetDatabase(true) { + + @Override + protected void load() { + } + + }; + Application.setComponentPresetDao(componentPresetDao); + + OpenRocketDocument doc = new GeneralRocketLoader().load( + new File("3d-Test-Junk/net/sf/openrocket/startup/al1 Apocalypse_54mmtestFr.rkt.xml"), + new DatabaseMotorFinder()); + + StorageOptions saver = new StorageOptions(); + saver.setIncludeDecals(true); + + new OpenRocketSaver().save(new File("3d-Test-Junk/net/sf/openrocket/startup/Apocalypse-ork.zip"), doc, saver); + + } +} diff --git a/core/3d-Test-Junk/net/sf/openrocket/startup/TextureTest.java b/core/3d-Test-Junk/net/sf/openrocket/startup/TextureTest.java index 8b69d561d..4424c0dd5 100644 --- a/core/3d-Test-Junk/net/sf/openrocket/startup/TextureTest.java +++ b/core/3d-Test-Junk/net/sf/openrocket/startup/TextureTest.java @@ -1,5 +1,6 @@ package net.sf.openrocket.startup; import java.awt.BorderLayout; +import java.io.File; import java.lang.reflect.Method; import javax.swing.JFrame; @@ -10,7 +11,7 @@ import net.sf.openrocket.database.ComponentPresetDatabase; import net.sf.openrocket.database.ThrustCurveMotorSetDatabase; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DatabaseMotorFinder; -import net.sf.openrocket.file.rocksim.importt.RocksimLoader; +import net.sf.openrocket.file.GeneralRocketLoader; import net.sf.openrocket.gui.main.componenttree.ComponentTree; import net.sf.openrocket.gui.scalefigure.RocketPanel; import net.sf.openrocket.gui.util.GUIUtil; @@ -47,22 +48,14 @@ public class TextureTest { @Override protected void load() { - ConcurrentComponentPresetDatabaseLoader presetLoader = new ConcurrentComponentPresetDatabaseLoader( this ); - presetLoader.load(); - try { - presetLoader.await(); - } catch ( InterruptedException iex) { - - } } }; - componentPresetDao.load("datafiles", ".*csv"); - componentPresetDao.startLoading(); Application.setComponentPresetDao(componentPresetDao); - OpenRocketDocument doc = new RocksimLoader().load( - TextureTest.class.getResourceAsStream("al1 Apocalypse_54mmtestFr.rkt.xml"), new DatabaseMotorFinder()); + OpenRocketDocument doc = new GeneralRocketLoader().load( + new File("3d-Test-Junk/net/sf/openrocket/startup/al1 Apocalypse_54mmtestFr.rkt.xml"), + new DatabaseMotorFinder()); GUIUtil.setBestLAF(); diff --git a/core/3d-Test-Junk/net/sf/openrocket/startup/al1 Apocalypse_54mmtestFr.rkt.xml b/core/3d-Test-Junk/net/sf/openrocket/startup/al1 Apocalypse_54mmtestFr.rkt.xml index 03b59ae46..a3ba3fe10 100644 --- a/core/3d-Test-Junk/net/sf/openrocket/startup/al1 Apocalypse_54mmtestFr.rkt.xml +++ b/core/3d-Test-Junk/net/sf/openrocket/startup/al1 Apocalypse_54mmtestFr.rkt.xml @@ -233,7 +233,7 @@ Plastic nose cone 0. 0. - file=(3d-Test-Junk/net/sf/openrocket/startup/Apocalypse_CONE_pointsFrfl.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) + file=(Apocalypse_CONE_pointsFrfl.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(1)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) 1. 0. @@ -292,7 +292,7 @@ 0. 0. - file=(3d-Test-Junk/net/sf/openrocket/startup/Apocalypse_logo_HAUT_fr2.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(2,1,1)|repeat=(1)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(0) + file=(Apocalypse_logo_HAUT_fr2.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(2,1,1)|repeat=(1)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(0) 1. 0. @@ -460,7 +460,7 @@ 0. 0. - file=(3d-Test-Junk/net/sf/openrocket/startup/Apocalypse_logo_medium2.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(2,1,1)|repeat=(1)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(0)) + file=(Apocalypse_logo_medium2.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(2,1,1)|repeat=(1)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(0)) 1. 0. @@ -571,7 +571,7 @@ 0. 0. - file=(3d-Test-Junk/net/sf/openrocket/startup/Apocalypse_logo_bas_coup2.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(2,1,1)|repeat=(1)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(0) + file=(Apocalypse_logo_bas_coup2.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(2,1,1)|repeat=(1)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(0) 1. 0. @@ -851,7 +851,7 @@ 0 0. 0. - file=(3d-Test-Junk/net/sf/openrocket/startup/fin rad-test5.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(0)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) + file=(fin rad-test5.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(0)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) 1. 0. @@ -1095,7 +1095,7 @@ 0 0. 1.5708 - file=(3d-Test-Junk/net/sf/openrocket/startup/fin rad-test5.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(0)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) + file=(fin rad-test5.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(0)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) 1. 0. @@ -1339,7 +1339,7 @@ 0 0. 3.14159 - file=(3d-Test-Junk/net/sf/openrocket/startup/fin rad-test5.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(0)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) + file=(fin rad-test5.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(0)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) 1. 0. @@ -1583,7 +1583,7 @@ 0 0. -1.5708 - file=(3d-Test-Junk/net/sf/openrocket/startup/fin rad-test5.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(0)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) + file=(fin rad-test5.jpg)|position=(0,0,0)|origin=(0.5,0.5,0.5)|scale=(1,1,1)|repeat=(0)|interpolate=(0)|flipr(0)|flips(0)|flipt=(0)|preventseam=(1) 1. 0. diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index fd1f9152f..5152407ca 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1041,6 +1041,8 @@ StorageOptChooser.lbl.seconds = seconds StorageOptChooser.rdbut.Onlyprimfig = Only primary figures StorageOptChooser.lbl.longC1 = Store only the values shown in the summary table.
StorageOptChooser.lbl.longC2 = This results in the smallest files. +StorageOptChooser.checkbox.IncludeDecals = Include decals +StorageOptChooser.lbl.IncludeDecals = "Including decals will produce a compressed zip file" StorageOptChooser.checkbox.Compfile = Compress file StorageOptChooser.lbl.UsingComp = Using compression reduces the file size significantly. StorageOptChooser.lbl.longD1 = An estimate on how large the resulting file would be with the present options. diff --git a/core/src/net/sf/openrocket/appearance/AppearanceBuilder.java b/core/src/net/sf/openrocket/appearance/AppearanceBuilder.java index 44a299b0b..2341908ec 100644 --- a/core/src/net/sf/openrocket/appearance/AppearanceBuilder.java +++ b/core/src/net/sf/openrocket/appearance/AppearanceBuilder.java @@ -1,7 +1,5 @@ package net.sf.openrocket.appearance; -import java.net.URL; - import net.sf.openrocket.appearance.Decal.EdgeMode; import net.sf.openrocket.util.AbstractChangeSource; import net.sf.openrocket.util.Color; @@ -26,7 +24,7 @@ public class AppearanceBuilder extends AbstractChangeSource { private double centerU, centerV; private double scaleU, scaleV; private double rotation; - private URL image; + private String image; private Decal.EdgeMode edgeMode; public AppearanceBuilder() { @@ -47,7 +45,7 @@ public class AppearanceBuilder extends AbstractChangeSource { setScale(d.getScale().x, d.getScale().y); setRotation(d.getRotation()); setEdgeMode(d.getEdgeMode()); - setImage(d.getImageURL()); + setImage(d.getImage()); } // TODO Critical the rest of this! } @@ -199,11 +197,11 @@ public class AppearanceBuilder extends AbstractChangeSource { fireChangeEvent(); } - public URL getImage() { + public String getImage() { return image; } - public void setImage(URL image) { + public void setImage(String image) { this.image = image; fireChangeEvent(); } diff --git a/core/src/net/sf/openrocket/appearance/Decal.java b/core/src/net/sf/openrocket/appearance/Decal.java index 84507ac35..e32c6efac 100644 --- a/core/src/net/sf/openrocket/appearance/Decal.java +++ b/core/src/net/sf/openrocket/appearance/Decal.java @@ -1,7 +1,5 @@ package net.sf.openrocket.appearance; -import java.net.URL; - import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Coordinate; @@ -26,11 +24,11 @@ public class Decal { private final Coordinate offset, center, scale; private final double rotation; - private final URL image; + private final String image; private final EdgeMode mode; - + Decal(final Coordinate offset, final Coordinate center, final Coordinate scale, final double rotation, - final URL image, final EdgeMode mode) { + final String image, final EdgeMode mode) { this.offset = offset; this.center = center; this.scale = scale; @@ -59,7 +57,7 @@ public class Decal { return mode; } - public URL getImageURL() { + public String getImage() { return image; } diff --git a/core/src/net/sf/openrocket/document/DecalRegistry.java b/core/src/net/sf/openrocket/document/DecalRegistry.java new file mode 100644 index 000000000..f74c0a45e --- /dev/null +++ b/core/src/net/sf/openrocket/document/DecalRegistry.java @@ -0,0 +1,54 @@ +package net.sf.openrocket.document; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class DecalRegistry { + + private File baseFile; + private boolean isZipFile = false; + + /* FIXME - Caching ? + private Map cache = new HashMap(); + */ + + public void setBaseFile(File baseFile) { + this.baseFile = baseFile; + } + + public void setIsZipFile( boolean isZipFile ) { + this.isZipFile = isZipFile; + } + + public InputStream getDecal( String name ) throws FileNotFoundException, IOException { + /* FIXME - Caching? + byte[] bytes = cache.get(name); + if ( bytes != null ) { + return new ByteArrayInputStream(bytes); + } + */ + if ( isZipFile ) { + ZipInputStream zis = new ZipInputStream(new FileInputStream(baseFile)); + ZipEntry entry = zis.getNextEntry(); + while ( entry != null ) { + if ( entry.getName().equals(name) ) { + return zis; + } + entry = zis.getNextEntry(); + } + } + + if ( baseFile != null ) { + File decal = new File(baseFile.getParentFile(), name); + // FIXME - update cache + return new FileInputStream(decal); + } + throw new FileNotFoundException( "Unable to locate decal for name " + name ); + } + +} diff --git a/core/src/net/sf/openrocket/document/OpenRocketDocument.java b/core/src/net/sf/openrocket/document/OpenRocketDocument.java index 12535220b..4a3851d69 100644 --- a/core/src/net/sf/openrocket/document/OpenRocketDocument.java +++ b/core/src/net/sf/openrocket/document/OpenRocketDocument.java @@ -59,8 +59,6 @@ public class OpenRocketDocument implements ComponentChangeListener { private final ArrayList simulations = new ArrayList(); private ArrayList customExpressions = new ArrayList(); - - /* * The undo/redo variables and mechanism are documented in doc/undo-redo-flow.* */ @@ -85,6 +83,7 @@ public class OpenRocketDocument implements ComponentChangeListener { private String nextDescription = null; private String storedDescription = null; + private DecalRegistry decalRegistry = new DecalRegistry(); private ArrayList undoRedoListeners = new ArrayList(2); @@ -176,6 +175,15 @@ public class OpenRocketDocument implements ComponentChangeListener { } + public DecalRegistry getDecalRegistry() { + return decalRegistry; + } + + + public void setDecalRegistry(DecalRegistry decalRegistry) { + this.decalRegistry = decalRegistry; + } + public File getFile() { return file; } diff --git a/core/src/net/sf/openrocket/file/AbstractRocketLoader.java b/core/src/net/sf/openrocket/file/AbstractRocketLoader.java index f0c7464df..c4804e3bd 100644 --- a/core/src/net/sf/openrocket/file/AbstractRocketLoader.java +++ b/core/src/net/sf/openrocket/file/AbstractRocketLoader.java @@ -15,32 +15,6 @@ public abstract class AbstractRocketLoader implements RocketLoader { protected final WarningSet warnings = new WarningSet(); - /** - * Loads a rocket from the specified File object. - */ - @Override - public final OpenRocketDocument load(File source, MotorFinder motorFinder) throws RocketLoadException { - warnings.clear(); - InputStream stream = null; - - try { - - stream = new BufferedInputStream(new FileInputStream(source)); - return load(stream, motorFinder); - - } catch (FileNotFoundException e) { - throw new RocketLoadException("File not found: " + source); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - /** * Loads a rocket from the specified InputStream. */ diff --git a/core/src/net/sf/openrocket/file/GeneralRocketLoader.java b/core/src/net/sf/openrocket/file/GeneralRocketLoader.java index da6858d89..4a2773a42 100644 --- a/core/src/net/sf/openrocket/file/GeneralRocketLoader.java +++ b/core/src/net/sf/openrocket/file/GeneralRocketLoader.java @@ -1,6 +1,8 @@ package net.sf.openrocket.file; import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; @@ -9,6 +11,7 @@ import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.openrocket.importt.OpenRocketLoader; import net.sf.openrocket.file.rocksim.importt.RocksimLoader; @@ -23,8 +26,10 @@ import net.sf.openrocket.util.TextUtil; * * @author Sampo Niskanen */ -public class GeneralRocketLoader extends AbstractRocketLoader { +public class GeneralRocketLoader { + protected final WarningSet warnings = new WarningSet(); + private static final int READ_BYTES = 300; private static final byte[] GZIP_SIGNATURE = { 31, -117 }; // 0x1f, 0x8b @@ -36,7 +41,46 @@ public class GeneralRocketLoader extends AbstractRocketLoader { private final RocksimLoader rocksimLoader = new RocksimLoader(); - @Override + /** + * Loads a rocket from the specified File object. + */ + public final OpenRocketDocument load(File source, MotorFinder motorFinder) throws RocketLoadException { + warnings.clear(); + InputStream stream = null; + + try { + + stream = new BufferedInputStream(new FileInputStream(source)); + OpenRocketDocument doc = load(stream, source, motorFinder); + return doc; + + } catch (Exception e) { + throw new RocketLoadException("Exception loading file: " + source,e); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public final OpenRocketDocument load(InputStream source, File file, MotorFinder motorFinder) throws RocketLoadException { + try { + OpenRocketDocument doc = loadFromStream(source, motorFinder ); + doc.getDecalRegistry().setBaseFile(file); + return doc; + } catch (Exception e) { + throw new RocketLoadException("Exception loading stream", e); + } + } + + public final WarningSet getWarnings() { + return warnings; + } + protected OpenRocketDocument loadFromStream(InputStream source, MotorFinder motorFinder) throws IOException, RocketLoadException { @@ -56,12 +100,14 @@ public class GeneralRocketLoader extends AbstractRocketLoader { throw new RocketLoadException("Unsupported or corrupt file."); } + // Detect the appropriate loader // Check for GZIP if (buffer[0] == GZIP_SIGNATURE[0] && buffer[1] == GZIP_SIGNATURE[1]) { OpenRocketDocument doc = loadFromStream(new GZIPInputStream(source), motorFinder); doc.getDefaultStorageOptions().setCompressionEnabled(true); + doc.getDecalRegistry().setIsZipFile(false); return doc; } @@ -77,6 +123,7 @@ public class GeneralRocketLoader extends AbstractRocketLoader { if (entry.getName().matches(".*\\.[oO][rR][kK]$")) { OpenRocketDocument doc = loadFromStream(in, motorFinder); doc.getDefaultStorageOptions().setCompressionEnabled(true); + doc.getDecalRegistry().setIsZipFile(true); return doc; } else if ( entry.getName().matches(".*\\.[rR][kK][tT]$")) { OpenRocketDocument doc = loadFromStream(in, motorFinder); @@ -91,7 +138,9 @@ public class GeneralRocketLoader extends AbstractRocketLoader { if (buffer[i] == OPENROCKET_SIGNATURE[match]) { match++; if (match == OPENROCKET_SIGNATURE.length) { - return loadUsing(source, openRocketLoader, motorFinder); + OpenRocketDocument doc = loadUsing(openRocketLoader, source, motorFinder); + doc.getDecalRegistry().setIsZipFile(false); + return doc; } } else { match = 0; @@ -100,12 +149,14 @@ public class GeneralRocketLoader extends AbstractRocketLoader { byte[] typeIdentifier = ArrayUtils.copyOf(buffer, ROCKSIM_SIGNATURE.length); if (Arrays.equals(ROCKSIM_SIGNATURE, typeIdentifier)) { - return loadUsing(source, rocksimLoader, motorFinder); + OpenRocketDocument doc = loadUsing(rocksimLoader, source, motorFinder); + doc.getDecalRegistry().setIsZipFile(false); + return doc; } throw new RocketLoadException("Unsupported or corrupt file."); } - private OpenRocketDocument loadUsing(InputStream source, RocketLoader loader, MotorFinder motorFinder) + private OpenRocketDocument loadUsing(RocketLoader loader, InputStream source, MotorFinder motorFinder) throws RocketLoadException { warnings.clear(); OpenRocketDocument doc = loader.load(source, motorFinder); diff --git a/core/src/net/sf/openrocket/file/RocketLoader.java b/core/src/net/sf/openrocket/file/RocketLoader.java index 81bcb0ac8..cbe9c724c 100644 --- a/core/src/net/sf/openrocket/file/RocketLoader.java +++ b/core/src/net/sf/openrocket/file/RocketLoader.java @@ -1,6 +1,5 @@ package net.sf.openrocket.file; -import java.io.File; import java.io.InputStream; import net.sf.openrocket.aerodynamics.WarningSet; @@ -8,8 +7,6 @@ import net.sf.openrocket.document.OpenRocketDocument; public interface RocketLoader { - public OpenRocketDocument load(File source, MotorFinder motorFinder) throws RocketLoadException; - public OpenRocketDocument load(InputStream source, MotorFinder motorFinder) throws RocketLoadException; public WarningSet getWarnings(); diff --git a/core/src/net/sf/openrocket/file/RocketSaver.java b/core/src/net/sf/openrocket/file/RocketSaver.java index 5c18bb8de..6ee50071b 100644 --- a/core/src/net/sf/openrocket/file/RocketSaver.java +++ b/core/src/net/sf/openrocket/file/RocketSaver.java @@ -36,7 +36,7 @@ public abstract class RocketSaver { throws IOException { OutputStream s = new BufferedOutputStream(new FileOutputStream(dest)); try { - save(s, document, options); + save(dest.getName(), s, document, options); } finally { s.close(); } @@ -50,8 +50,8 @@ public abstract class RocketSaver { * @param doc the document to save. * @throws IOException in case of an I/O error. */ - public final void save(OutputStream dest, OpenRocketDocument doc) throws IOException { - save(dest, doc, doc.getDefaultStorageOptions()); + public final void save(String fileName, OutputStream dest, OpenRocketDocument doc) throws IOException { + save(fileName, dest, doc, doc.getDefaultStorageOptions()); } @@ -63,7 +63,7 @@ public abstract class RocketSaver { * @param options the storage options. * @throws IOException in case of an I/O error. */ - public abstract void save(OutputStream dest, OpenRocketDocument doc, + public abstract void save(String fileName, OutputStream dest, OpenRocketDocument doc, StorageOptions options) throws IOException; diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 0a5e04790..fb6a411d5 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -1,17 +1,26 @@ package net.sf.openrocket.file.openrocket; import java.io.BufferedWriter; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.zip.GZIPOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.appearance.AppearanceBuilder; +import net.sf.openrocket.appearance.Decal; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.document.StorageOptions; @@ -39,22 +48,22 @@ import net.sf.openrocket.util.TextUtil; public class OpenRocketSaver extends RocketSaver { private static final LogHelper log = Application.getLogger(); - - + + /** * Divisor used in converting an integer version to the point-represented version. * The integer version divided by this value is the major version and the remainder is * the minor version. For example 101 corresponds to file version "1.1". */ public static final int FILE_VERSION_DIVISOR = 100; - - + + private static final String OPENROCKET_CHARSET = "UTF-8"; - + private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket.savers"; private static final String METHOD_SUFFIX = "Saver"; - - + + // Estimated storage used by different portions // These have been hand-estimated from saved files private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590; @@ -63,44 +72,163 @@ public class OpenRocketSaver extends RocketSaver { private static final int BYTES_PER_SIMULATION_COMPRESSED = 100; private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350; private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100; - - + + private int indent; private Writer dest; - + @Override - public void save(OutputStream output, OpenRocketDocument document, StorageOptions options) + public void save(String fileName, OutputStream output, OpenRocketDocument document, StorageOptions options) throws IOException { + + if (!options.isIncludeDecals()) { + saveInternal(output,document,options); + return; + } + + // tweak the options - no need to double compress: + options.setCompressionEnabled(false); + + ZipOutputStream zos = new ZipOutputStream(output); + zos.setLevel(9); + + /* if we want a directory ... + String path = fileName; + int dotlocation = fileName.lastIndexOf('.'); + if ( dotlocation > 1 ) { + path = fileName.substring(dotlocation); + } + */ + + // grab the set of decal images. + Map decals = new HashMap(); + Map decalNameNormalization = new HashMap(); + try { + for( RocketComponent c : document.getRocket() ) { + + if ( c.getAppearance() == null ) { + continue; + } + + Appearance ap = c.getAppearance(); + + if ( ap.getTexture() == null ) { + continue; + } + + Decal decal = ap.getTexture(); + + String decalName = decal.getImage(); + + if ( decals.containsKey(decalName) ) { + continue; + } + + InputStream is = document.getDecalRegistry().getDecal(decalName); + + decals.put(decalName, is); + + // Normalize the name: + File fname = new File(decalName); + String newName = "decals/" + fname.getName(); + + decalNameNormalization.put(decalName, newName); + + } + } + catch (IOException ex) { + for ( InputStream is: decals.values() ) { + try { + is.close(); + } + catch ( Throwable t ) { + } + } + throw ex; + } + // Now we have to loop through all the components and update their names. + + for( RocketComponent c : document.getRocket() ) { + + if ( c.getAppearance() == null ) { + continue; + } + + Appearance ap = c.getAppearance(); + + if ( ap.getTexture() == null ) { + continue; + } + + AppearanceBuilder builder = new AppearanceBuilder(ap); + + builder.setImage( decalNameNormalization.get(ap.getTexture().getImage())); + + c.setAppearance(builder.getAppearance()); + + } + + // Fixme - should probably be the same name? Should we put everything in a directory? + ZipEntry mainFile = new ZipEntry("rocket.ork"); + zos.putNextEntry(mainFile); + saveInternal(zos,document,options); + zos.closeEntry(); + + // Now we write out all the decal images files. + + for( Map.Entry image : decals.entrySet() ) { + + String newName = decalNameNormalization.get(image.getKey()); + ZipEntry decal = new ZipEntry(newName); + zos.putNextEntry(decal); + + InputStream is = image.getValue(); + int bytesRead = 0; + byte[] buffer = new byte[2048]; + while( (bytesRead = is.read(buffer)) > 0 ) { + zos.write(buffer, 0, bytesRead); + } + zos.closeEntry(); + } + + zos.flush(); + zos.close(); + + } + + public void saveInternal(OutputStream output, OpenRocketDocument document, StorageOptions options) + throws IOException { + log.info("Saving .ork file"); - + if (options.isCompressionEnabled()) { log.debug("Enabling compression"); output = new GZIPOutputStream(output); } - + dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET)); - + // Select file version number final int fileVersion = calculateNecessaryFileVersion(document, options); final String fileVersionString = (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR); log.debug("Storing file version " + fileVersionString); - - + + this.indent = 0; - - + + writeln(""); writeln(""); indent++; - + // Recursively save the rocket structure saveComponent(document.getRocket()); - + writeln(""); - + // Save custom expressions; saveCustomDatatypes(document); @@ -168,6 +296,8 @@ public class OpenRocketSaver extends RocketSaver { long size = 0; + // FIXME - estimate decals + // Size per component int componentCount = 0; Rocket rocket = doc.getRocket(); @@ -176,20 +306,20 @@ public class OpenRocketSaver extends RocketSaver { iterator.next(); componentCount++; } - + if (options.isCompressionEnabled()) size += componentCount * BYTES_PER_COMPONENT_COMPRESSED; else size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED; - - + + // Size per simulation if (options.isCompressionEnabled()) size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED; else size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED; - - + + // Size per flight data point int pointCount = 0; double timeSkip = options.getSimulationTimeSkip(); @@ -203,16 +333,16 @@ public class OpenRocketSaver extends RocketSaver { } } } - + if (options.isCompressionEnabled()) size += pointCount * BYTES_PER_DATAPOINT_COMPRESSED; else size += pointCount * BYTES_PER_DATAPOINT_UNCOMPRESSED; - + return size; } - - + + /** * Determine which file version is required in order to store all the features of the * current design. By default the oldest version that supports all the necessary features @@ -224,6 +354,9 @@ public class OpenRocketSaver extends RocketSaver { */ private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) { /* + * File version 1.6 is required for: + * - saving files using appearances and textures + * * File version 1.5 is requires for: * - saving designs using ComponentPrests * - recovery device deployment on lower stage separation @@ -239,14 +372,21 @@ public class OpenRocketSaver extends RocketSaver { * * Otherwise use version 1.0. */ - + + // Search the rocket for any Appearnces (version 1.6) + for (RocketComponent c : document.getRocket()) { + if (c.getAppearance() != null) { + return FILE_VERSION_DIVISOR + 6; + } + } + // Search the rocket for any ComponentPresets (version 1.5) for (RocketComponent c : document.getRocket()) { if (c.getPresetComponent() != null) { return FILE_VERSION_DIVISOR + 5; } } - + // Search for recovery device deployment type LOWER_STAGE_SEPARATION (version 1.5) for (RocketComponent c : document.getRocket()) { if (c instanceof RecoveryDevice) { @@ -260,17 +400,17 @@ public class OpenRocketSaver extends RocketSaver { if (!document.getCustomExpressions().isEmpty()) { return FILE_VERSION_DIVISOR + 5; } - + // Check if design has simulations defined (version 1.4) if (document.getSimulationCount() > 0) { return FILE_VERSION_DIVISOR + 4; } - + // Check for motor definitions (version 1.4) for (RocketComponent c : document.getRocket()) { if (!(c instanceof MotorMount)) continue; - + MotorMount mount = (MotorMount) c; for (String id : document.getRocket().getMotorConfigurationIDs()) { if (mount.getMotor(id) != null) { @@ -278,7 +418,7 @@ public class OpenRocketSaver extends RocketSaver { } } } - + // Check for fin tabs (version 1.1) for (RocketComponent c : document.getRocket()) { // Check for fin tabs @@ -289,7 +429,7 @@ public class OpenRocketSaver extends RocketSaver { return FILE_VERSION_DIVISOR + 1; } } - + // Check for components attached to tube coupler if (c instanceof TubeCoupler) { if (c.getChildCount() > 0) { @@ -297,45 +437,45 @@ public class OpenRocketSaver extends RocketSaver { } } } - + // Default (version 1.0) return FILE_VERSION_DIVISOR + 0; } - - - + + + @SuppressWarnings("unchecked") private void saveComponent(RocketComponent component) throws IOException { - + log.debug("Saving component " + component.getComponentName()); - + Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX, "getElements", RocketComponent.class); if (m == null) { throw new BugException("Unable to find saving class for component " + component.getComponentName()); } - + // Get the strings to save List list = (List) m.invokeStatic(component); int length = list.size(); - + if (length == 0) // Nothing to do return; - + if (length < 2) { throw new RuntimeException("BUG, component data length less than two lines."); } - + // Open element writeln(list.get(0)); indent++; - + // Write parameters for (int i = 1; i < length - 1; i++) { writeln(list.get(i)); } - + // Recursively write subcomponents if (component.getChildCount() > 0) { writeln(""); @@ -351,25 +491,25 @@ public class OpenRocketSaver extends RocketSaver { indent--; writeln(""); } - + // Close element indent--; writeln(list.get(length - 1)); } - - + + private void saveSimulation(Simulation simulation, double timeSkip) throws IOException { SimulationOptions cond = simulation.getOptions(); - + writeln(""); indent++; - + writeln("" + escapeXML(simulation.getName()) + ""); // TODO: MEDIUM: Other simulators/calculators - + writeln("RK4Simulator"); writeln("BarrowmanCalculator"); - + writeln(""); indent++; @@ -383,7 +523,7 @@ public class OpenRocketSaver extends RocketSaver { writeElement("launchlatitude", cond.getLaunchLatitude()); writeElement("launchlongitude", cond.getLaunchLongitude()); writeElement("geodeticmethod", cond.getGeodeticComputation().name().toLowerCase(Locale.ENGLISH)); - + if (cond.isISAAtmosphere()) { writeln(""); } else { @@ -394,19 +534,19 @@ public class OpenRocketSaver extends RocketSaver { indent--; writeln(""); } - + writeElement("timestep", cond.getTimeStep()); - + indent--; writeln(""); - - + + for (String s : simulation.getSimulationListeners()) { writeElement("listener", escapeXML(s)); } - + // Write basic simulation data - + FlightData data = simulation.getSimulatedData(); if (data != null) { String str = ""); } - + indent--; writeln(""); - + } - - - + + + private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) throws IOException { double previousTime = -100000; - + if (branch == null) return; - + // Retrieve the types from the branch FlightDataType[] types = branch.getTypes(); - + if (types.length == 0) return; - + // Retrieve the data from the branch List> data = new ArrayList>(types.length); for (int i = 0; i < types.length; i++) { data.add(branch.get(types[i])); } List timeData = branch.get(FlightDataType.TYPE_TIME); - + // Build the tag StringBuilder sb = new StringBuilder(); sb.append(""); writeln(sb.toString()); indent++; - + // Write events for (FlightEvent event : branch.getEvents()) { writeln(""); } - + // Write the data int length = branch.getLength(); if (length > 0) { writeDataPointString(data, 0, sb); previousTime = timeData.get(0); } - + for (int i = 1; i < length - 1; i++) { if (timeData != null) { if (Math.abs(timeData.get(i) - previousTime - timeSkip) < Math.abs(timeData.get(i + 1) - previousTime - timeSkip)) { @@ -527,61 +667,61 @@ public class OpenRocketSaver extends RocketSaver { writeDataPointString(data, i, sb); } } - + if (length > 1) { writeDataPointString(data, length - 1, sb); } - + indent--; writeln(""); } - - - + + + /* TODO: LOW: This is largely duplicated from above! */ private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) { int count = 0; - + double previousTime = -100000; - + if (branch == null) return 0; - + // Retrieve the types from the branch FlightDataType[] types = branch.getTypes(); - + if (types.length == 0) return 0; - + List timeData = branch.get(FlightDataType.TYPE_TIME); if (timeData == null) { // If time data not available, store all points return branch.getLength(); } - + // Write the data int length = branch.getLength(); if (length > 0) { count++; previousTime = timeData.get(0); } - + for (int i = 1; i < length - 1; i++) { if (Math.abs(timeData.get(i) - previousTime - timeSkip) < Math.abs(timeData.get(i + 1) - previousTime - timeSkip)) { count++; previousTime = timeData.get(i); } } - + if (length > 1) { count++; } - + return count; } - - - + + + private void writeDataPointString(List> data, int index, StringBuilder sb) throws IOException { sb.setLength(0); @@ -594,17 +734,17 @@ public class OpenRocketSaver extends RocketSaver { sb.append(""); writeln(sb.toString()); } - - - + + + private void writeElement(String element, Object content) throws IOException { if (content == null) content = ""; writeln("<" + element + ">" + content + ""); } - - - + + + private void writeln(String str) throws IOException { if (str.length() == 0) { dest.write("\n"); @@ -616,10 +756,10 @@ public class OpenRocketSaver extends RocketSaver { s = s + str + "\n"; dest.write(s); } - - - - + + + + /** * Return the XML equivalent of an enum name. * @@ -629,5 +769,5 @@ public class OpenRocketSaver extends RocketSaver { public static String enumToXMLName(Enum e) { return e.name().toLowerCase(Locale.ENGLISH).replace("_", ""); } - + } diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java index 0915a9353..55a0b786f 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java @@ -13,6 +13,8 @@ import java.util.regex.Pattern; import net.sf.openrocket.aerodynamics.Warning; import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.appearance.AppearanceBuilder; +import net.sf.openrocket.appearance.Decal.EdgeMode; import net.sf.openrocket.database.Databases; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; @@ -166,7 +168,7 @@ public class OpenRocketLoader extends AbstractRocketLoader { class DocumentConfig { /* Remember to update OpenRocketSaver as well! */ - public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5" }; + public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6"}; /** * Divisor used in converting an integer version to the point-represented version. @@ -744,6 +746,7 @@ class DatatypeHandler extends AbstractElementHandler { } class CustomExpressionHandler extends AbstractElementHandler { + @SuppressWarnings("unused") private final DocumentLoadingContext context; private final OpenRocketContentHandler contentHandler; public CustomExpression currentExpression; @@ -854,6 +857,9 @@ class ComponentParameterHandler extends AbstractElementHandler { if (element.equals("subcomponents")) { return new ComponentHandler(component, context); } + if ( element.equals("appearance")) { + return new AppearanceHandler(component,context); + } if (element.equals("motormount")) { if (!(component instanceof MotorMount)) { warnings.add(Warning.fromString("Illegal component defined as motor mount.")); @@ -885,7 +891,8 @@ class ComponentParameterHandler extends AbstractElementHandler { String content, WarningSet warnings) { if (element.equals("subcomponents") || element.equals("motormount") || - element.equals("finpoints") || element.equals("motorconfiguration")) { + element.equals("finpoints") || element.equals("motorconfiguration") || + element.equals("appearance")) { return; } @@ -913,6 +920,95 @@ class ComponentParameterHandler extends AbstractElementHandler { } } +class AppearanceHandler extends AbstractElementHandler { + @SuppressWarnings("unused") + private final DocumentLoadingContext context; + private final RocketComponent component; + + private final AppearanceBuilder builder = new AppearanceBuilder(); + private boolean isInDecal = false; + public AppearanceHandler( RocketComponent component, DocumentLoadingContext context ) { + this.context = context; + this.component = component; + } + @Override + public ElementHandler openElement(String element,HashMap attributes, WarningSet warnings) + throws SAXException { + if ( "decal".equals(element) ) { + String name = attributes.remove("name"); + builder.setImage(name); + double rotation = Double.parseDouble(attributes.remove("rotation")); + builder.setRotation(rotation); + String edgeModeName = attributes.remove("edgemode"); + EdgeMode edgeMode = EdgeMode.valueOf(edgeModeName); + builder.setEdgeMode(edgeMode); + isInDecal = true; + return this; + } + return PlainTextHandler.INSTANCE; + } + @Override + public void closeElement(String element,HashMap attributes, String content, WarningSet warnings) throws SAXException { + if ( "ambient".equals(element) ) { + int red = Integer.parseInt(attributes.get("red")); + int green = Integer.parseInt(attributes.get("green")); + int blue = Integer.parseInt(attributes.get("blue")); + builder.setAmbient( new Color(red,green,blue)); + return; + } + if ( "diffuse".equals(element) ) { + int red = Integer.parseInt(attributes.get("red")); + int green = Integer.parseInt(attributes.get("green")); + int blue = Integer.parseInt(attributes.get("blue")); + builder.setDiffuse( new Color(red,green,blue)); + return; + } + if ( "specular".equals(element) ) { + int red = Integer.parseInt(attributes.get("red")); + int green = Integer.parseInt(attributes.get("green")); + int blue = Integer.parseInt(attributes.get("blue")); + builder.setSpecular( new Color(red,green,blue)); + return; + } + if ( isInDecal && "center".equals(element) ) { + double x = Double.parseDouble(attributes.get("x")); + double y = Double.parseDouble(attributes.get("y")); + builder.setCenter(x,y); + return; + } + if ( isInDecal && "offset".equals(element) ) { + double x = Double.parseDouble(attributes.get("x")); + double y = Double.parseDouble(attributes.get("y")); + builder.setOffset(x,y); + return; + } + if ( isInDecal && "scale".equals(element) ) { + double x = Double.parseDouble(attributes.get("x")); + double y = Double.parseDouble(attributes.get("y")); + builder.setScale(x,y); + return; + } + if( isInDecal && "decal".equals(element) ) { + isInDecal = false; + return; + } + + super.closeElement(element, attributes, content, warnings); + } + + @Override + public void endHandler(String element, HashMap attributes, + String content, WarningSet warnings) throws SAXException { + if ( "decal".equals(element) ) { + isInDecal = false; + return; + } + component.setAppearance(builder.getAppearance()); + super.endHandler(element, attributes, content, warnings); + } + +} + /** * A handler that reads the specifications within the freeformfinset's diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java index a4892902a..93601af8a 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java @@ -5,6 +5,9 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.appearance.Decal; +import net.sf.openrocket.appearance.Decal.EdgeMode; import net.sf.openrocket.file.RocketSaver; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.material.Material; @@ -18,51 +21,73 @@ import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Color; +import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LineStyle; - public class RocketComponentSaver { private static final Translator trans = Application.getTranslator(); - + protected RocketComponentSaver() { // Prevent instantiation from outside the package } - + protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { elements.add("" + RocketSaver.escapeXML(c.getName()) + ""); - + ComponentPreset preset = c.getPresetComponent(); if (preset != null) { elements.add(""); } - - + + Appearance ap = c.getAppearance(); + if ( ap != null ) { + elements.add(""); + Color ambient = ap.getAmbient(); + emitColor("ambient",elements,ambient); + Color diffuse = ap.getDiffuse(); + emitColor("diffuse",elements,diffuse); + Color specular = ap.getSpecular(); + emitColor("specular",elements,specular); + Decal decal = ap.getTexture(); + if ( decal != null ) { + String name = decal.getImage(); + double rotation = decal.getRotation(); + EdgeMode edgeMode = decal.getEdgeMode(); + elements.add(""); + Coordinate center = decal.getCenter(); + elements.add("
"); + Coordinate offset = decal.getOffset(); + elements.add(""); + Coordinate scale = decal.getScale(); + elements.add(""); + elements.add(""); + } + elements.add(""); + } + // Save color and line style if significant if (!(c instanceof Rocket || c instanceof ComponentAssembly)) { Color color = c.getColor(); - if (color != null) { - elements.add(""); - } - + emitColor("color", elements, color); + LineStyle style = c.getLineStyle(); if (style != null) { // Type names currently equivalent to the enum names except for case. elements.add("" + style.name().toLowerCase(Locale.ENGLISH) + ""); } } - - + + // Save position unless "AFTER" if (c.getRelativePosition() != RocketComponent.Position.AFTER) { // The type names are currently equivalent to the enum names except for case. String type = c.getRelativePosition().name().toLowerCase(Locale.ENGLISH); elements.add("" + c.getPositionValue() + ""); } - - + + // Overrides boolean overridden = false; if (c.isMassOverridden()) { @@ -77,26 +102,26 @@ public class RocketComponentSaver { elements.add("" + c.getOverrideSubcomponents() + ""); } - - + + // Comment if (c.getComment().length() > 0) { elements.add("" + RocketSaver.escapeXML(c.getComment()) + ""); } - + } - - - - + + + + protected final String materialParam(Material mat) { return materialParam("material", mat); } - - + + protected final String materialParam(String tag, Material mat) { String str = "<" + tag; - + switch (mat.getType()) { case LINE: str += " type=\"line\""; @@ -110,29 +135,29 @@ public class RocketComponentSaver { default: throw new BugException("Unknown material type: " + mat.getType()); } - + String baseName = trans.getBaseText("material", mat.getName()); - + return str + " density=\"" + mat.getDensity() + "\">" + RocketSaver.escapeXML(baseName) + ""; } - - + + protected final List motorMountParams(MotorMount mount) { if (!mount.isMotorMount()) return Collections.emptyList(); - + String[] motorConfigIDs = ((RocketComponent) mount).getRocket().getMotorConfigurationIDs(); List elements = new ArrayList(); - + elements.add(""); - + for (String id : motorConfigIDs) { Motor motor = mount.getMotor(id); - + // Nothing is stored if no motor loaded if (motor == null) continue; - + elements.add(" "); if (motor.getMotorType() != Motor.Type.UNKNOWN) { elements.add(" " + motor.getMotorType().name().toLowerCase(Locale.ENGLISH) + ""); @@ -146,27 +171,35 @@ public class RocketComponentSaver { elements.add(" " + RocketSaver.escapeXML(motor.getDesignation()) + ""); elements.add(" " + motor.getDiameter() + ""); elements.add(" " + motor.getLength() + ""); - + // Motor delay if (mount.getMotorDelay(id) == Motor.PLUGGED) { elements.add(" none"); } else { elements.add(" " + mount.getMotorDelay(id) + ""); } - + elements.add(" "); } - + elements.add(" " + mount.getIgnitionEvent().name().toLowerCase(Locale.ENGLISH).replace("_", "") + ""); - + elements.add(" " + mount.getIgnitionDelay() + ""); elements.add(" " + mount.getMotorOverhang() + ""); - + elements.add(""); - + return elements; } - + + private final static void emitColor( String elementName, List elements, Color color ) { + if (color != null) { + elements.add("<" + elementName+ " red=\"" + color.getRed() + "\" green=\"" + color.getGreen() + + "\" blue=\"" + color.getBlue() + "\"/>"); + } + + } + } diff --git a/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java b/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java index 36f44d527..c3b4522ff 100644 --- a/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java +++ b/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java @@ -1,5 +1,14 @@ package net.sf.openrocket.file.rocksim.export; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.StorageOptions; import net.sf.openrocket.file.RocketSaver; @@ -12,14 +21,6 @@ import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.Stage; import net.sf.openrocket.startup.Application; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.Marshaller; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.StringWriter; - /** * This class is responsible for converting an OpenRocket design to a Rocksim design. */ @@ -55,7 +56,7 @@ public class RocksimSaver extends RocketSaver { } @Override - public void save(OutputStream dest, OpenRocketDocument doc, StorageOptions options) throws IOException { + public void save(String fileName, OutputStream dest, OpenRocketDocument doc, StorageOptions options) throws IOException { log.info("Saving .rkt file"); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(dest, "UTF-8")); diff --git a/core/src/net/sf/openrocket/file/rocksim/importt/RockSimAppearanceBuilder.java b/core/src/net/sf/openrocket/file/rocksim/importt/RockSimAppearanceBuilder.java index 14734782e..397e63750 100644 --- a/core/src/net/sf/openrocket/file/rocksim/importt/RockSimAppearanceBuilder.java +++ b/core/src/net/sf/openrocket/file/rocksim/importt/RockSimAppearanceBuilder.java @@ -82,7 +82,7 @@ public class RockSimAppearanceBuilder extends AppearanceBuilder { //Find out how to get path of current rocksim file //so I can look in it's directory } - setImage(f.toURI().toURL()); + setImage(value); } } else if ("repeat".equals(name)) { repeat = "1".equals(value); @@ -136,6 +136,23 @@ public class RockSimAppearanceBuilder extends AppearanceBuilder { } static Color parseColor(String s) { + // blue and white came from a real file. + if ( "blue".equals(s) ) { + return new Color(0,0,255); + } + if ( "white".equals(s) ) { + return new Color(255,255,255); + } + // I guessed these are valid color names in Rksim. + if ( "red".equals(s) ) { + return new Color(255,0,0); + } + if( "green".equals(s) ) { + return new Color(0,255,0); + } + if ( "black".equals(s) ) { + return new Color(0,0,0); + } s = s.replace("rgb(", ""); s = s.replace(")", ""); String ss[] = s.split(","); diff --git a/core/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java b/core/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java index b147bde45..0a6dc27a4 100644 --- a/core/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java +++ b/core/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java @@ -5,8 +5,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; import java.util.EventObject; import javax.swing.JButton; @@ -233,25 +231,12 @@ public class AppearancePanel extends JPanel { choose.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - URL u = ab.getImage(); File current = lastImageDir; - if (u != null && u.getProtocol().equals("file")) { - try { - current = new File(u.toURI()).getParentFile(); - } catch (Exception e1) { - e1.printStackTrace(); - } - } lastImageDir = current; JFileChooser fc = new JFileChooser(current); if (fc.showOpenDialog(AppearancePanel.this) == JFileChooser.APPROVE_OPTION) { - try { - ab.setImage(fc.getSelectedFile().toURI().toURL()); - } catch (MalformedURLException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } + ab.setImage(fc.getSelectedFile().getAbsolutePath()); } } diff --git a/core/src/net/sf/openrocket/gui/figure3d/FigureRenderStrategy.java b/core/src/net/sf/openrocket/gui/figure3d/FigureRenderStrategy.java index a8e25ed6c..594f510b3 100644 --- a/core/src/net/sf/openrocket/gui/figure3d/FigureRenderStrategy.java +++ b/core/src/net/sf/openrocket/gui/figure3d/FigureRenderStrategy.java @@ -19,6 +19,10 @@ import net.sf.openrocket.util.Color; public class FigureRenderStrategy extends RenderStrategy { private final float[] color = new float[4]; + public FigureRenderStrategy() { + super(null); + } + @Override public boolean isDrawn(RocketComponent c) { return true; diff --git a/core/src/net/sf/openrocket/gui/figure3d/RealisticRenderStrategy.java b/core/src/net/sf/openrocket/gui/figure3d/RealisticRenderStrategy.java index 03e8b58d4..45bbf7307 100644 --- a/core/src/net/sf/openrocket/gui/figure3d/RealisticRenderStrategy.java +++ b/core/src/net/sf/openrocket/gui/figure3d/RealisticRenderStrategy.java @@ -1,8 +1,7 @@ package net.sf.openrocket.gui.figure3d; +import java.io.InputStream; import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; import java.util.HashMap; import java.util.Map; @@ -14,26 +13,34 @@ import javax.media.opengl.GLProfile; import javax.media.opengl.fixedfunc.GLLightingFunc; import javax.media.opengl.fixedfunc.GLMatrixFunc; -import com.jogamp.opengl.util.texture.Texture; -import com.jogamp.opengl.util.texture.TextureData; -import com.jogamp.opengl.util.texture.TextureIO; - import net.sf.openrocket.appearance.Appearance; import net.sf.openrocket.appearance.Decal; +import net.sf.openrocket.document.DecalRegistry; +import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Color; +import com.jogamp.opengl.util.texture.Texture; +import com.jogamp.opengl.util.texture.TextureData; +import com.jogamp.opengl.util.texture.TextureIO; + public class RealisticRenderStrategy extends RenderStrategy { private final float[] colorBlack = { 0, 0, 0, 1 }; private final float[] color = new float[4]; private static final LogHelper log = Application.getLogger(); + private final DecalRegistry decalLoader; private boolean needClearCache = false; - private Map oldTexCache = new HashMap(); - private Map texCache = new HashMap(); + private Map oldTexCache = new HashMap(); + private Map texCache = new HashMap(); + + public RealisticRenderStrategy(OpenRocketDocument document) { + super(document); + this.decalLoader = document.getDecalRegistry(); + } @Override public void updateFigure() { @@ -42,8 +49,8 @@ public class RealisticRenderStrategy extends RenderStrategy { @Override public void init(GLAutoDrawable drawable) { - oldTexCache = new HashMap(); - texCache = new HashMap(); + oldTexCache = new HashMap(); + texCache = new HashMap(); } @Override @@ -100,7 +107,7 @@ public class RealisticRenderStrategy extends RenderStrategy { if (t != null && tex != null) { gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); - + tex.enable(gl); tex.bind(gl); gl.glMatrixMode(GL.GL_TEXTURE); @@ -138,46 +145,40 @@ public class RealisticRenderStrategy extends RenderStrategy { private void clearCaches(GL2 gl) { log.debug("ClearCaches"); - for (Map.Entry e : oldTexCache.entrySet()) { + for (Map.Entry e : oldTexCache.entrySet()) { log.debug("Destroying Texture for " + e.getKey()); if (e.getValue() != null) e.getValue().destroy(gl); } oldTexCache = texCache; - texCache = new HashMap(); + texCache = new HashMap(); } private Texture getTexture(Decal t) { - URL url = t.getImageURL(); - URI uri; // NEVER use a URL as a key! - try { - uri = url.toURI(); - } catch (URISyntaxException e) { - e.printStackTrace(); - return null; - } + String imageName = t.getImage(); // Return the Cached value if available - if (texCache.containsKey(uri)) - return texCache.get(uri); + if (texCache.containsKey(imageName)) + return texCache.get(imageName); // If the texture is in the Old Cache, save it. - if (oldTexCache.containsKey(uri)) { - texCache.put(uri, oldTexCache.get(uri)); - oldTexCache.remove(uri); - return texCache.get(uri); + if (oldTexCache.containsKey(imageName)) { + texCache.put(imageName, oldTexCache.get(imageName)); + oldTexCache.remove(imageName); + return texCache.get(imageName); } // Otherwise load it. Texture tex = null; try { log.debug("Loading texture " + t); - TextureData data = TextureIO.newTextureData(GLProfile.getDefault(), url.openStream(), true, null); + InputStream is = decalLoader.getDecal(imageName); + TextureData data = TextureIO.newTextureData(GLProfile.getDefault(), is, true, null); tex = TextureIO.newTexture(data); } catch (Throwable e) { log.error("Error loading Texture", e); } - texCache.put(uri, tex); + texCache.put(imageName, tex); return tex; diff --git a/core/src/net/sf/openrocket/gui/figure3d/RenderStrategy.java b/core/src/net/sf/openrocket/gui/figure3d/RenderStrategy.java index e73a18405..4908307a9 100644 --- a/core/src/net/sf/openrocket/gui/figure3d/RenderStrategy.java +++ b/core/src/net/sf/openrocket/gui/figure3d/RenderStrategy.java @@ -3,9 +3,17 @@ package net.sf.openrocket.gui.figure3d; import javax.media.opengl.GL2; import javax.media.opengl.GLAutoDrawable; +import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.rocketcomponent.RocketComponent; public abstract class RenderStrategy { + + protected final OpenRocketDocument document; + + public RenderStrategy( OpenRocketDocument document ) { + this.document = document; + } + public abstract boolean isDrawn(RocketComponent c); public abstract boolean isDrawnTransparent(RocketComponent c); diff --git a/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java b/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java index 91d963a8e..86661be9c 100644 --- a/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java +++ b/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java @@ -31,6 +31,7 @@ import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.event.MouseInputAdapter; +import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.gui.figureelements.CGCaret; import net.sf.openrocket.gui.figureelements.CPCaret; import net.sf.openrocket.gui.figureelements.FigureElement; @@ -65,6 +66,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener { private static double fovX = Double.NaN; private static final int CARET_SIZE = 20; + private OpenRocketDocument document; private Configuration configuration; private GLCanvas canvas; @@ -87,7 +89,8 @@ public class RocketFigure3d extends JPanel implements GLEventListener { RocketRenderer rr = new RocketRenderer(); - public RocketFigure3d(Configuration config) { + public RocketFigure3d(OpenRocketDocument document, Configuration config) { + this.document = document; this.configuration = config; this.setLayout(new BorderLayout()); @@ -627,7 +630,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener { if ( t == TYPE_FIGURE ){ rr.setRenderStrategy(new FigureRenderStrategy()); } else { - rr.setRenderStrategy(new RealisticRenderStrategy()); + rr.setRenderStrategy(new RealisticRenderStrategy(document)); } repaint(); } diff --git a/core/src/net/sf/openrocket/gui/main/BasicFrame.java b/core/src/net/sf/openrocket/gui/main/BasicFrame.java index 2b59e9cc6..e84c9968a 100644 --- a/core/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/core/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -113,7 +113,7 @@ public class BasicFrame extends JFrame { /** * The RocketLoader instance used for loading all rocket designs. */ - private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader(); + private static final GeneralRocketLoader ROCKET_LOADER = new GeneralRocketLoader(); private static final RocketSaver ROCKET_SAVER = new OpenRocketSaver(); diff --git a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 2cef70809..6760685de 100644 --- a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -164,7 +164,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change // Create figure and custom scroll pane figure = new RocketFigure(configuration); - figure3d = new RocketFigure3d(configuration); + figure3d = new RocketFigure3d(document, configuration); figureHolder = new JPanel(new BorderLayout()); diff --git a/core/src/net/sf/openrocket/gui/util/OpenFileWorker.java b/core/src/net/sf/openrocket/gui/util/OpenFileWorker.java index da193c165..3d93bf568 100644 --- a/core/src/net/sf/openrocket/gui/util/OpenFileWorker.java +++ b/core/src/net/sf/openrocket/gui/util/OpenFileWorker.java @@ -12,7 +12,7 @@ import javax.swing.SwingWorker; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DatabaseMotorFinder; -import net.sf.openrocket.file.RocketLoader; +import net.sf.openrocket.file.GeneralRocketLoader; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; @@ -28,22 +28,22 @@ public class OpenFileWorker extends SwingWorker { private final File file; private final InputStream stream; - private final RocketLoader loader; + private final GeneralRocketLoader loader; - public OpenFileWorker(File file, RocketLoader loader) { + public OpenFileWorker(File file, GeneralRocketLoader loader) { this.file = file; this.stream = null; this.loader = loader; } - public OpenFileWorker(InputStream stream, RocketLoader loader) { + public OpenFileWorker(InputStream stream, GeneralRocketLoader loader) { this.stream = stream; this.file = null; this.loader = loader; } - public RocketLoader getRocketLoader() { + public GeneralRocketLoader getRocketLoader() { return loader; } @@ -67,7 +67,7 @@ public class OpenFileWorker extends SwingWorker { is = new ProgressInputStream(is); try { - return loader.load(is, new DatabaseMotorFinder()); + return loader.load(is, file, new DatabaseMotorFinder()); } finally { try { is.close(); diff --git a/core/src/net/sf/openrocket/gui/util/SaveFileWorker.java b/core/src/net/sf/openrocket/gui/util/SaveFileWorker.java index 1ccc3145b..39e883730 100644 --- a/core/src/net/sf/openrocket/gui/util/SaveFileWorker.java +++ b/core/src/net/sf/openrocket/gui/util/SaveFileWorker.java @@ -41,8 +41,10 @@ public class SaveFileWorker extends SwingWorker { }; + String rawFilename = file.getName(); + try { - saver.save(os, document); + saver.save(rawFilename, os, document); } finally { try { os.close(); diff --git a/core/test/net/sf/openrocket/IntegrationTest.java b/core/test/net/sf/openrocket/IntegrationTest.java index b06a6c34c..702e7ebfe 100644 --- a/core/test/net/sf/openrocket/IntegrationTest.java +++ b/core/test/net/sf/openrocket/IntegrationTest.java @@ -1,8 +1,13 @@ package net.sf.openrocket; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.awt.event.ActionEvent; +import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -91,7 +96,7 @@ public class IntegrationTest extends BaseTestCase { GeneralRocketLoader loader = new GeneralRocketLoader(); InputStream is = this.getClass().getResourceAsStream("simplerocket.ork"); assertNotNull("Problem in unit test, cannot find simplerocket.ork", is); - document = loader.load(is, new DatabaseMotorFinder()); + document = loader.load(is, new File("simplerocket.ork"), new DatabaseMotorFinder()); is.close(); undoAction = UndoRedoAction.newUndoAction(document);