Save OBJ export options in preferences

This commit is contained in:
SiboVG 2023-08-18 03:32:06 +02:00
parent c38d5f59a6
commit 8e7c6afed3
10 changed files with 192 additions and 50 deletions

View File

@ -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<String> 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<DocumentChangeListener> listeners = new ArrayList<DocumentChangeListener>();
@ -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;
}
/**

View File

@ -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;
}
}

View File

@ -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

View File

@ -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.

View File

@ -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) {

View File

@ -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");

View File

@ -28,9 +28,13 @@ public class OBJOptionChooser extends JPanel {
private final JCheckBox triangulate;
private final JComboBox<ObjUtils.LevelOfDetail> LOD;
private final List<RocketComponent> selectedComponents;
public OBJOptionChooser(OBJExportOptions opts, List<RocketComponent> 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<RocketComponent> 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<RocketComponent> selectedComponents) {
boolean onlyComponentAssemblies = true;
for (RocketComponent component : selectedComponents) {
if (!(component instanceof ComponentAssembly)) {
onlyComponentAssemblies = false;
break;
}
}
return onlyComponentAssemblies;
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -132,6 +132,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
//////////////////////
@Override
public Preferences getPreferences() {
return PREFNODE;
}