diff --git a/core/src/net/sf/openrocket/document/OpenRocketDocument.java b/core/src/net/sf/openrocket/document/OpenRocketDocument.java index 614e8cec7..0b3332818 100644 --- a/core/src/net/sf/openrocket/document/OpenRocketDocument.java +++ b/core/src/net/sf/openrocket/document/OpenRocketDocument.java @@ -3,8 +3,8 @@ package net.sf.openrocket.document; import java.io.File; import java.util.*; -import net.sf.openrocket.file.wavefrontobj.export.OBJExportOptions; import net.sf.openrocket.rocketcomponent.*; +import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.util.StateChangeListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +36,7 @@ import net.sf.openrocket.util.ArrayList; */ public class OpenRocketDocument implements ComponentChangeListener, StateChangeListener { private static final Logger log = LoggerFactory.getLogger(OpenRocketDocument.class); + private static final Preferences prefs = Application.getPreferences(); private final List file_extensions = Arrays.asList("ork", "ork.gz", "rkt", "rkt.gz"); // Possible extensions of an OpenRocket document /** * The minimum number of undo levels that are stored. @@ -92,8 +93,7 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL private int savedID = -1; private final StorageOptions storageOptions = new StorageOptions(); - private final OBJExportOptions objOptions; - + private final DecalRegistry decalRegistry = new DecalRegistry(); private final List listeners = new ArrayList(); @@ -105,7 +105,6 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL */ OpenRocketDocument(Rocket rocket) { this.rocket = rocket; - this.objOptions = new OBJExportOptions(rocket); rocket.enableEvents(); init(); } @@ -242,19 +241,15 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL else this.savedID = rocket.getModID() + modID; } - + /** * Retrieve the default storage options for this document. - * + * * @return the storage options. */ public StorageOptions getDefaultStorageOptions() { return storageOptions; } - - public OBJExportOptions getDefaultOBJOptions() { - return objOptions; - } /** diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/CoordTransform.java b/core/src/net/sf/openrocket/file/wavefrontobj/CoordTransform.java index 762122046..6551a1135 100644 --- a/core/src/net/sf/openrocket/file/wavefrontobj/CoordTransform.java +++ b/core/src/net/sf/openrocket/file/wavefrontobj/CoordTransform.java @@ -139,17 +139,6 @@ public class CoordTransform { return new DefaultFloatTuple((float) xRotTrans, (float) yRotTrans, (float) zRotTrans); } - /** - * Returns the equivalent axis for the x-axis in the OpenRocket coordinate system (axial axis). - * ! The direction is relative to the OpenRocket x-axis ! For instance, if the OBJ coordinate system uses the z-axis - * that runs in the opposite direction of the OR x-axis (z-axis runs from the bottom of the rocket to the tip), you - * must return Axis.Z_MIN. - * @return the equivalent axis for the x-axis in the OpenRocket coordinate system (axial axis) - */ - public Axis getAxialAxis() { - return axialAxis; - } - /** * Returns the transformed coordinate for the given axis. * @param axis the OpenRocket axis to transform @@ -204,4 +193,39 @@ public class CoordTransform { default -> throw new IllegalStateException("Unknown axis"); }; } + + public Axis getXAxis() { + return xAxis; + } + + public Axis getYAxis() { + return yAxis; + } + + public Axis getZAxis() { + return zAxis; + } + + /** + * Returns the equivalent axis for the x-axis in the OpenRocket coordinate system (axial axis). + * ! The direction is relative to the OpenRocket x-axis ! For instance, if the OBJ coordinate system uses the z-axis + * that runs in the opposite direction of the OR x-axis (z-axis runs from the bottom of the rocket to the tip), you + * must return Axis.Z_MIN. + * @return the equivalent axis for the x-axis in the OpenRocket coordinate system (axial axis) + */ + public Axis getAxialAxis() { + return axialAxis; + } + + public double getOrigXOffs() { + return origXOffs; + } + + public double getOrigYOffs() { + return origYOffs; + } + + public double getOrigZOffs() { + return origZOffs; + } } diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java b/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java index af3f2a6d7..6878d41a0 100644 --- a/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java +++ b/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java @@ -6,7 +6,6 @@ import de.javagl.obj.Obj; import de.javagl.obj.ObjFace; import de.javagl.obj.ObjGroup; import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Coordinate; @@ -24,22 +23,37 @@ public class ObjUtils { * Level of detail to use for the export. */ public enum LevelOfDetail { - LOW_QUALITY(25, trans.get("LevelOfDetail.LOW_QUALITY")), - NORMAL(60, trans.get("LevelOfDetail.NORMAL")), - HIGH_QUALITY(100, trans.get("LevelOfDetail.HIGH_QUALITY")); + LOW_QUALITY(25, trans.get("LevelOfDetail.LOW_QUALITY"), "LOW"), + NORMAL(60, trans.get("LevelOfDetail.NORMAL"), "NORMAL"), + HIGH_QUALITY(100, trans.get("LevelOfDetail.HIGH_QUALITY"), "HIGH"); private final int value; private final String label; + private final String exportLabel; - LevelOfDetail(int value, String label) { + LevelOfDetail(int value, String label, String exportLabel) { this.value = value; this.label = label; + this.exportLabel = exportLabel; } public int getValue() { return value; } + public String getExportLabel() { + return exportLabel; + } + + public static LevelOfDetail fromExportLabel(String exportLabel) { + for (LevelOfDetail lod : LevelOfDetail.values()) { + if (lod.getExportLabel().equals(exportLabel)) { + return lod; + } + } + return LevelOfDetail.NORMAL; + } + /** * Get a dynamic estimate of the number of sides (= vertices in a perfect circle) to be used for the given radius. * @param radius The radius to estimate the number of sides for diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java index 4c4f60ef0..32080941e 100644 --- a/core/src/net/sf/openrocket/startup/Preferences.java +++ b/core/src/net/sf/openrocket/startup/Preferences.java @@ -9,19 +9,17 @@ import java.util.Map; import java.util.Set; import net.sf.openrocket.database.Databases; +import net.sf.openrocket.file.wavefrontobj.Axis; +import net.sf.openrocket.file.wavefrontobj.CoordTransform; +import net.sf.openrocket.file.wavefrontobj.ObjUtils; +import net.sf.openrocket.file.wavefrontobj.export.OBJExportOptions; import net.sf.openrocket.material.Material; import net.sf.openrocket.models.atmosphere.AtmosphericModel; import net.sf.openrocket.models.atmosphere.ExtendedISAModel; import net.sf.openrocket.preset.ComponentPreset; -import net.sf.openrocket.rocketcomponent.BodyComponent; -import net.sf.openrocket.rocketcomponent.FinSet; -import net.sf.openrocket.rocketcomponent.InternalComponent; -import net.sf.openrocket.rocketcomponent.LaunchLug; import net.sf.openrocket.rocketcomponent.MassObject; -import net.sf.openrocket.rocketcomponent.RailButton; -import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.rocketcomponent.TubeFinSet; import net.sf.openrocket.simulation.RK4SimulationStepper; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.BuildProperties; @@ -120,7 +118,25 @@ public abstract class Preferences implements ChangeSource { public static final String GEODETIC_COMPUTATION = "GeodeticComputationStrategy"; public static final String UI_THEME = "UITheme"; - + + // OBJ Export options + private static final String OBJ_EXPORT_OPTIONS_NODE = "OBJExportOptions"; + private static final String OBJ_EXPORT_CHILDREN = "ExportChildren"; + private static final String OBJ_EXPORT_APPEARANCE = "ExportAppearance"; + private static final String OBJ_EXPORT_AS_SEPARATE_FILES = "ExportAsSeparateFiles"; + private static final String OBJ_REMOVE_OFFSET = "RemoveOffset"; + private static final String OBJ_TRIANGULATE = "Triangulate"; + private static final String OBJ_LOD = "LOD"; + private static final String OBJ_SCALING = "Scaling"; + //// Coordinate transformer + private static final String OBJ_TRANSFORMER_NODE = "CoordTransform"; + private static final String OBJ_X_AXIS = "xAxis"; + private static final String OBJ_Y_AXIS = "yAxis"; + private static final String OBJ_Z_AXIS = "zAxis"; + private static final String OBJ_AXIAL_AXIS = "AxialAxis"; + private static final String OBJ_ORIG_X_OFFS = "OrigXOffs"; + private static final String OBJ_ORIG_Y_OFFS = "OrigYOffs"; + private static final String OBJ_ORIG_Z_OFFS = "OrigZOffs"; private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel(); @@ -159,6 +175,8 @@ public abstract class Preferences implements ChangeSource { public abstract java.util.prefs.Preferences getNode(String nodeName); + public abstract java.util.prefs.Preferences getPreferences(); + /* * Welcome dialog */ @@ -1015,6 +1033,67 @@ public abstract class Preferences implements ChangeSource { public void setUITheme(Object theme) {} + public void saveOBJExportOptions(OBJExportOptions options) { + // ! Don't forget to update the loadOBJExportOptions method and OBJOptionChooser.storeOptions if you add new options ! + + java.util.prefs.Preferences preferences = getPreferences(); + java.util.prefs.Preferences objExportOptionsNode = preferences.node(OBJ_EXPORT_OPTIONS_NODE); + + objExportOptionsNode.putBoolean(OBJ_EXPORT_CHILDREN, options.isExportChildren()); + objExportOptionsNode.putBoolean(OBJ_EXPORT_APPEARANCE, options.isExportAppearance()); + objExportOptionsNode.putBoolean(OBJ_EXPORT_AS_SEPARATE_FILES, options.isExportAsSeparateFiles()); + objExportOptionsNode.putBoolean(OBJ_REMOVE_OFFSET, options.isRemoveOffset()); + objExportOptionsNode.putBoolean(OBJ_TRIANGULATE, options.isTriangulate()); + + objExportOptionsNode.putFloat(OBJ_SCALING, options.getScaling()); + + objExportOptionsNode.put(OBJ_LOD, options.getLOD().getExportLabel()); + + // Save CoordTransform + java.util.prefs.Preferences coordTransformNode = objExportOptionsNode.node(OBJ_TRANSFORMER_NODE); + CoordTransform transform = options.getTransformer(); + + coordTransformNode.put(OBJ_X_AXIS, transform.getXAxis().toString()); + coordTransformNode.put(OBJ_Y_AXIS, transform.getYAxis().toString()); + coordTransformNode.put(OBJ_Z_AXIS, transform.getZAxis().toString()); + coordTransformNode.put(OBJ_AXIAL_AXIS, transform.getAxialAxis().toString()); + coordTransformNode.putDouble(OBJ_ORIG_X_OFFS, transform.getOrigXOffs()); + coordTransformNode.putDouble(OBJ_ORIG_Y_OFFS, transform.getOrigYOffs()); + coordTransformNode.putDouble(OBJ_ORIG_Z_OFFS, transform.getOrigZOffs()); + } + + public OBJExportOptions loadOBJExportOptions(Rocket rocket) { + java.util.prefs.Preferences preferences = getPreferences(); + java.util.prefs.Preferences objExportOptionsNode = preferences.node(OBJ_EXPORT_OPTIONS_NODE); + + OBJExportOptions options = new OBJExportOptions(rocket); + options.setExportChildren(objExportOptionsNode.getBoolean(OBJ_EXPORT_CHILDREN, false)); + options.setExportAppearance(objExportOptionsNode.getBoolean(OBJ_EXPORT_APPEARANCE, false)); + options.setExportAsSeparateFiles(objExportOptionsNode.getBoolean(OBJ_EXPORT_AS_SEPARATE_FILES, false)); + options.setRemoveOffset(objExportOptionsNode.getBoolean(OBJ_REMOVE_OFFSET, true)); + options.setTriangulate(objExportOptionsNode.getBoolean(OBJ_TRIANGULATE, false)); + + options.setScaling(objExportOptionsNode.getFloat(OBJ_SCALING, 1)); + + options.setLOD(ObjUtils.LevelOfDetail.fromExportLabel( + objExportOptionsNode.get(OBJ_LOD, ObjUtils.LevelOfDetail.NORMAL.getExportLabel()))); + + // Load CoordTransform + java.util.prefs.Preferences coordTransformNode = objExportOptionsNode.node(OBJ_TRANSFORMER_NODE); + + Axis xAxis = Axis.fromString(coordTransformNode.get(OBJ_X_AXIS, Axis.Y.toString())); + Axis yAxis = Axis.fromString(coordTransformNode.get(OBJ_Y_AXIS, Axis.Z.toString())); + Axis zAxis = Axis.fromString(coordTransformNode.get(OBJ_Z_AXIS, Axis.X_MIN.toString())); + Axis axialAxis = Axis.fromString(coordTransformNode.get(OBJ_AXIAL_AXIS, Axis.Z_MIN.toString())); + double origXOffs = coordTransformNode.getDouble(OBJ_ORIG_X_OFFS, 0.0); + double origYOffs = coordTransformNode.getDouble(OBJ_ORIG_Y_OFFS, 0.0); + double origZOffs = coordTransformNode.getDouble(OBJ_ORIG_Z_OFFS, rocket.getLength()); + + CoordTransform transform = new CoordTransform(xAxis, yAxis, zAxis, axialAxis, origXOffs, origYOffs, origZOffs); + options.setTransformer(transform); + + return options; + } /* * Within a holder class so they will load only when needed. diff --git a/core/test/net/sf/openrocket/ServicesForTesting.java b/core/test/net/sf/openrocket/ServicesForTesting.java index f336343bc..a3b7ed34b 100644 --- a/core/test/net/sf/openrocket/ServicesForTesting.java +++ b/core/test/net/sf/openrocket/ServicesForTesting.java @@ -158,6 +158,11 @@ public class ServicesForTesting extends AbstractModule { public java.util.prefs.Preferences getNode(String nodeName) { return getBaseNode().node(nodeName); } + + @Override + public java.util.prefs.Preferences getPreferences() { + return getBaseNode(); + } private java.util.prefs.Preferences getBaseNode() { if (root == null) { diff --git a/core/test/net/sf/openrocket/startup/MockPreferences.java b/core/test/net/sf/openrocket/startup/MockPreferences.java index 8b9bb6685..22301defc 100644 --- a/core/test/net/sf/openrocket/startup/MockPreferences.java +++ b/core/test/net/sf/openrocket/startup/MockPreferences.java @@ -81,7 +81,12 @@ public class MockPreferences extends Preferences { public java.util.prefs.Preferences getNode(String nodeName) { return NODE.node(nodeName); } - + + @Override + public java.util.prefs.Preferences getPreferences() { + return NODE; + } + @Override public void addUserMaterial(Material m) { throw new UnsupportedOperationException("Not yet implemented"); diff --git a/swing/src/net/sf/openrocket/file/wavefrontobj/OBJOptionChooser.java b/swing/src/net/sf/openrocket/file/wavefrontobj/OBJOptionChooser.java index 336d8cecd..e62b1cf41 100644 --- a/swing/src/net/sf/openrocket/file/wavefrontobj/OBJOptionChooser.java +++ b/swing/src/net/sf/openrocket/file/wavefrontobj/OBJOptionChooser.java @@ -28,9 +28,13 @@ public class OBJOptionChooser extends JPanel { private final JCheckBox triangulate; private final JComboBox LOD; + private final List selectedComponents; + public OBJOptionChooser(OBJExportOptions opts, List selectedComponents) { super(new MigLayout()); + this.selectedComponents = selectedComponents; + // ------------ Basic options ------------ //// Export children this.exportChildren = new JCheckBox(trans.get("OBJOptionChooser.checkbox.exportChildren")); @@ -101,17 +105,11 @@ public class OBJOptionChooser extends JPanel { }); this.add(advancedOptionsPanel); - loadOptions(opts, selectedComponents); + loadOptions(opts); } - public void loadOptions(OBJExportOptions opts, List selectedComponents) { - boolean onlyComponentAssemblies = true; - for (RocketComponent component : selectedComponents) { - if (!(component instanceof ComponentAssembly)) { - onlyComponentAssemblies = false; - break; - } - } + public void loadOptions(OBJExportOptions opts) { + boolean onlyComponentAssemblies = isOnlyComponentAssembliesSelected(selectedComponents); if (onlyComponentAssemblies) { exportChildren.setEnabled(false); exportChildren.setSelected(true); @@ -131,11 +129,26 @@ public class OBJOptionChooser extends JPanel { } public void storeOptions(OBJExportOptions opts) { - opts.setExportChildren(exportChildren.isSelected()); + boolean onlyComponentAssemblies = isOnlyComponentAssembliesSelected(selectedComponents); + // Don't save the state when the checkbox is set automatically due to component assemblies + if (!onlyComponentAssemblies) { + opts.setExportChildren(exportChildren.isSelected()); + } opts.setExportAppearance(exportAppearance.isSelected()); opts.setExportAsSeparateFiles(exportAsSeparateFiles.isSelected()); opts.setRemoveOffset(removeOffset.isSelected()); opts.setTriangulate(triangulate.isSelected()); opts.setLOD((ObjUtils.LevelOfDetail) LOD.getSelectedItem()); } + + private static boolean isOnlyComponentAssembliesSelected(List selectedComponents) { + boolean onlyComponentAssemblies = true; + for (RocketComponent component : selectedComponents) { + if (!(component instanceof ComponentAssembly)) { + onlyComponentAssemblies = false; + break; + } + } + return onlyComponentAssemblies; + } } diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index 04ad5e366..8ba7cc37c 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -1406,7 +1406,9 @@ public class BasicFrame extends JFrame { } if (objChooser != null) { - objChooser.storeOptions(document.getDefaultOBJOptions()); + OBJExportOptions selectedOptions = new OBJExportOptions(rocket); + objChooser.storeOptions(selectedOptions); + prefs.saveOBJExportOptions(selectedOptions); } File file = chooser.getSelectedFile(); @@ -1640,7 +1642,8 @@ public class BasicFrame extends JFrame { } file = FileHelper.forceExtension(file, "obj"); - boolean isExportAsSeparateFiles = document.getDefaultOBJOptions().isExportAsSeparateFiles(); + OBJExportOptions options = prefs.loadOBJExportOptions(rocket); + boolean isExportAsSeparateFiles = options.isExportAsSeparateFiles(); if (isExportAsSeparateFiles || FileHelper.confirmWrite(file, BasicFrame.this)) { // No overwrite warning for separate files return saveAsWavefrontOBJ(file); } @@ -1648,7 +1651,7 @@ public class BasicFrame extends JFrame { } private boolean saveAsWavefrontOBJ(File file) { - OBJExportOptions options = document.getDefaultOBJOptions(); + OBJExportOptions options = prefs.loadOBJExportOptions(rocket); return saveWavefrontOBJFile(file, options); } diff --git a/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java b/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java index 4bf4d8832..cb17cb959 100644 --- a/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java +++ b/swing/src/net/sf/openrocket/gui/main/DesignFileSaveAsFileChooser.java @@ -9,9 +9,9 @@ import javax.swing.JFileChooser; import javax.swing.filechooser.FileFilter; import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.document.StorageOptions; import net.sf.openrocket.document.StorageOptions.FileType; import net.sf.openrocket.file.wavefrontobj.OBJOptionChooser; +import net.sf.openrocket.file.wavefrontobj.export.OBJExportOptions; import net.sf.openrocket.gui.util.FileHelper; import net.sf.openrocket.gui.util.SimpleFileFilter; import net.sf.openrocket.gui.util.SwingPreferences; @@ -19,6 +19,7 @@ import net.sf.openrocket.gui.widgets.SaveFileChooser; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.util.FileUtils; public class DesignFileSaveAsFileChooser extends SaveFileChooser { @@ -27,6 +28,7 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser { private final OpenRocketDocument document; private static final Translator trans = Application.getTranslator(); + private static final Preferences prefs = Application.getPreferences(); public static DesignFileSaveAsFileChooser build(OpenRocketDocument document, FileType type) { return new DesignFileSaveAsFileChooser(document, type, null); @@ -69,7 +71,8 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser { case WAVEFRONT_OBJ: defaultFilename = FileHelper.forceExtension(defaultFilename,"obj"); this.setDialogTitle(trans.get("saveAs.wavefront.title")); - OBJOptionChooser objChooser = new OBJOptionChooser(document.getDefaultOBJOptions(), selectedComponents); + OBJExportOptions initialOptions = prefs.loadOBJExportOptions(document.getRocket()); + OBJOptionChooser objChooser = new OBJOptionChooser(initialOptions, selectedComponents); this.setAccessory(objChooser); this.addChoosableFileFilter(FileHelper.WAVEFRONT_OBJ_FILTER); this.setFileFilter(FileHelper.WAVEFRONT_OBJ_FILTER); diff --git a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java index a7c135804..f0aa48b88 100644 --- a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java +++ b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java @@ -132,6 +132,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { ////////////////////// + @Override public Preferences getPreferences() { return PREFNODE; }