Add proper OBJ save dialog
This commit is contained in:
parent
9171de2520
commit
15d3bce807
@ -122,6 +122,7 @@ FileHelper.ALL_DESIGNS_FILTER = All rocket designs (*.ork; *.rkt, *.CDX1)
|
||||
FileHelper.OPENROCKET_DESIGN_FILTER = OpenRocket designs (*.ork)
|
||||
FileHelper.ROCKSIM_DESIGN_FILTER = RockSim designs (*.rkt)
|
||||
FileHelper.RASAERO_DESIGN_FILTER = RASAero designs (*.CDX1)
|
||||
FileHelper.WAVEFRONT_OBJ_FILTER = Wavefront OBJ 3D file (*.obj)
|
||||
FileHelper.OPEN_ROCKET_COMPONENT_FILTER = OpenRocket presets (*.orc)
|
||||
FileHelper.PNG_FILTER = PNG image (*.png)
|
||||
FileHelper.IMAGES = Image files
|
||||
@ -1413,6 +1414,7 @@ SaveRktWarningDialog.donotshow=Do not show this dialog again
|
||||
saveAs.openrocket.title = Save as OpenRocket ork file
|
||||
saveAs.rocksim.title = Export as RockSim rkt file
|
||||
saveAs.rasaero.title = Export as RASAero CDX1 file
|
||||
saveAs.wavefront.title = Export as Wavefront OBJ file
|
||||
|
||||
! RASAero exporting
|
||||
RASAeroExport.warning1 = Nose cone may not be flipped.
|
||||
@ -1481,6 +1483,27 @@ StorageOptChooser.lbl.info3 = Smallest file size
|
||||
StorageOptChooser.ttip.Saveopt = Save options
|
||||
StorageOptChooser.lbl.Saveopt = Save options
|
||||
|
||||
! OBJOptionChooser
|
||||
OBJOptionChooser.checkbox.exportChildren = Export children
|
||||
OBJOptionChooser.checkbox.exportChildren.ttip = If true, export children of the selected components as well.
|
||||
OBJOptionChooser.checkbox.exportChildren.assemblies.ttip = Component assemblies always export their children
|
||||
OBJOptionChooser.checkbox.exportAppearance = Export appearance
|
||||
OBJOptionChooser.checkbox.exportAppearance.ttip = If true, export the component appearances to an MTL file.
|
||||
OBJOptionChooser.checkbox.exportAsSeparateFiles = Export as separate files
|
||||
OBJOptionChooser.checkbox.exportAsSeparateFiles.ttip = If true, export each component as a separate OBJ file.
|
||||
OBJOptionChooser.checkbox.removeOffset = Remove offset
|
||||
OBJOptionChooser.checkbox.removeOffset.ttip = <html>If true, remove the offset of the component from the origin.<br>If false, the component is exported at its original location in the rocket.</html>
|
||||
OBJOptionChooser.btn.showAdvanced = Show Advanced options
|
||||
OBJOptionChooser.checkbox.triangulate = Triangulate mesh
|
||||
OBJOptionChooser.checkbox.triangulate.ttip = If true, triangulate the mesh before exporting (convert all quads or high-order polygons to a triangle).
|
||||
OBJOptionChooser.lbl.LevelOfDetail = Level of detail:
|
||||
OBJOptionChooser.lbl.LevelOfDetail.ttip = Select the desired level of detail of the geometry export.
|
||||
|
||||
! LevelOfDetail
|
||||
LevelOfDetail.LOW_QUALITY = Low quality
|
||||
LevelOfDetail.NORMAL = Normal quality
|
||||
LevelOfDetail.HIGH_QUALITY = High quality
|
||||
|
||||
! ThrustCurveMotorSelectionPanel
|
||||
TCMotorSelPan.lbl.Selrocketmotor = Select rocket motor:
|
||||
TCMotorSelPan.checkbox.hideSimilar = Hide very similar thrust curves
|
||||
|
@ -3,6 +3,7 @@ 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.util.StateChangeListener;
|
||||
import org.slf4j.Logger;
|
||||
@ -91,6 +92,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();
|
||||
|
||||
@ -103,6 +105,7 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL
|
||||
*/
|
||||
OpenRocketDocument(Rocket rocket) {
|
||||
this.rocket = rocket;
|
||||
this.objOptions = new OBJExportOptions(rocket);
|
||||
rocket.enableEvents();
|
||||
init();
|
||||
}
|
||||
@ -248,6 +251,10 @@ public class OpenRocketDocument implements ComponentChangeListener, StateChangeL
|
||||
public StorageOptions getDefaultStorageOptions() {
|
||||
return storageOptions;
|
||||
}
|
||||
|
||||
public OBJExportOptions getDefaultOBJOptions() {
|
||||
return objOptions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,8 @@ public class StorageOptions implements Cloneable {
|
||||
public enum FileType {
|
||||
OPENROCKET,
|
||||
ROCKSIM,
|
||||
RASAERO
|
||||
RASAERO,
|
||||
WAVEFRONT_OBJ
|
||||
}
|
||||
|
||||
private FileType fileType = FileType.OPENROCKET;
|
||||
|
@ -4,7 +4,9 @@ import de.javagl.obj.FloatTuple;
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
@ -15,19 +17,22 @@ import java.util.List;
|
||||
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
|
||||
*/
|
||||
public class ObjUtils {
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
/**
|
||||
* Level of detail to use for the export.
|
||||
*/
|
||||
public enum LevelOfDetail {
|
||||
LOW_QUALITY(25),
|
||||
NORMAL(60),
|
||||
HIGH_QUALITY(100);
|
||||
LOW_QUALITY(25, trans.get("LevelOfDetail.LOW_QUALITY")),
|
||||
NORMAL(60, trans.get("LevelOfDetail.NORMAL")),
|
||||
HIGH_QUALITY(100, trans.get("LevelOfDetail.HIGH_QUALITY"));
|
||||
|
||||
private final int value;
|
||||
private final String label;
|
||||
|
||||
LevelOfDetail(int value) {
|
||||
LevelOfDetail(int value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
@ -44,6 +49,11 @@ public class ObjUtils {
|
||||
final double refRadius = 0.05; // Reference radius for the number of sides (the "most common radius") <-- very arbitrary, oh well.
|
||||
return Math.max(MIN_SIDES, (int) (0.75*value + (radius/refRadius * 0.25*value))); // Adjust if needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,107 @@
|
||||
package net.sf.openrocket.file.wavefrontobj.export;
|
||||
|
||||
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
|
||||
import net.sf.openrocket.file.wavefrontobj.DefaultCoordTransform;
|
||||
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
|
||||
public class OBJExportOptions {
|
||||
/**
|
||||
* If true, export all children of the components as well
|
||||
*/
|
||||
private boolean exportChildren;
|
||||
/**
|
||||
* If true, export the appearance of the components to an MTL file.
|
||||
*/
|
||||
private boolean exportAppearance;
|
||||
/**
|
||||
* If true, export each component as a separate OBJ file.
|
||||
*/
|
||||
private boolean exportAsSeparateFiles;
|
||||
/**
|
||||
* If true, remove the offset of the object so it is centered at the origin (but the bottom of the object is at x=0).
|
||||
* 'x' being the longitudinal axis (depends on the used {@link CoordTransform}).
|
||||
*/
|
||||
private boolean removeOffset;
|
||||
/**
|
||||
* If true, triangulate all faces (convert quads and higher-order polygons to triangles)
|
||||
*/
|
||||
private boolean triangulate;
|
||||
/**
|
||||
* The level of detail to use for the export (e.g. low-quality, normal quality...).
|
||||
*/
|
||||
private ObjUtils.LevelOfDetail LOD;
|
||||
/**
|
||||
* The coordinate transformer to use for the export.
|
||||
* This is used to convert the coordinates from the rocket's coordinate system to the OBJ coordinate system (which is arbitrary).
|
||||
*/
|
||||
private CoordTransform transformer;
|
||||
|
||||
// TODO: scaling (to mm = x1000, or SI units)
|
||||
|
||||
public OBJExportOptions(Rocket rocket) {
|
||||
this.exportChildren = false;
|
||||
this.exportAppearance = false;
|
||||
this.exportAsSeparateFiles = false;
|
||||
this.removeOffset = true;
|
||||
this.triangulate = false;
|
||||
this.LOD = ObjUtils.LevelOfDetail.NORMAL;
|
||||
this.transformer = new DefaultCoordTransform(rocket.getLength());
|
||||
}
|
||||
|
||||
public boolean isExportChildren() {
|
||||
return exportChildren;
|
||||
}
|
||||
|
||||
public void setExportChildren(boolean exportChildren) {
|
||||
this.exportChildren = exportChildren;
|
||||
}
|
||||
|
||||
public boolean isRemoveOffset() {
|
||||
return removeOffset;
|
||||
}
|
||||
|
||||
public void setRemoveOffset(boolean removeOffset) {
|
||||
this.removeOffset = removeOffset;
|
||||
}
|
||||
|
||||
public boolean isTriangulate() {
|
||||
return triangulate;
|
||||
}
|
||||
|
||||
public void setTriangulate(boolean triangulate) {
|
||||
this.triangulate = triangulate;
|
||||
}
|
||||
|
||||
public boolean isExportAppearance() {
|
||||
return exportAppearance;
|
||||
}
|
||||
|
||||
public void setExportAppearance(boolean exportAppearance) {
|
||||
this.exportAppearance = exportAppearance;
|
||||
}
|
||||
|
||||
public ObjUtils.LevelOfDetail getLOD() {
|
||||
return LOD;
|
||||
}
|
||||
|
||||
public void setLOD(ObjUtils.LevelOfDetail LOD) {
|
||||
this.LOD = LOD;
|
||||
}
|
||||
|
||||
public CoordTransform getTransformer() {
|
||||
return transformer;
|
||||
}
|
||||
|
||||
public void setTransformer(CoordTransform transformer) {
|
||||
this.transformer = transformer;
|
||||
}
|
||||
|
||||
public boolean isExportAsSeparateFiles() {
|
||||
return exportAsSeparateFiles;
|
||||
}
|
||||
|
||||
public void setExportAsSeparateFiles(boolean exportAsSeparateFiles) {
|
||||
this.exportAsSeparateFiles = exportAsSeparateFiles;
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import net.sf.openrocket.rocketcomponent.RingComponent;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.rocketcomponent.Transition;
|
||||
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||
import net.sf.openrocket.util.FileUtils;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -55,12 +56,8 @@ import java.util.Set;
|
||||
public class OBJExporterFactory {
|
||||
private final List<RocketComponent> components;
|
||||
private final FlightConfiguration configuration;
|
||||
private final boolean exportChildren;
|
||||
private final boolean triangulate;
|
||||
private final boolean removeOffset;
|
||||
private final ObjUtils.LevelOfDetail LOD; // Level of detailed used for the export
|
||||
private final OBJExportOptions options;
|
||||
private final String filePath;
|
||||
private final CoordTransform transformer;
|
||||
|
||||
// The different exporters for each component
|
||||
private static final Map<Class<? extends RocketComponent>, ExporterFactory<?>> EXPORTER_MAP = Map.of(
|
||||
@ -79,38 +76,15 @@ public class OBJExporterFactory {
|
||||
* <b>NOTE: </b> you must call {@link #doExport()} to actually perform the export.
|
||||
* @param components List of components to export
|
||||
* @param configuration Flight configuration to use for the export
|
||||
* @param exportChildren If true, export all children of the components as well
|
||||
* @param triangulate If true, triangulate all faces
|
||||
* @param removeOffset If true, remove the offset of the object so it is centered at the origin (but the bottom of the object is at y=0)
|
||||
* @param LOD Level of detail to use for the export (e.g. '80')
|
||||
* @param transformer Coordinate system transformer to use to switch from the OpenRocket coordinate system to a custom OBJ coordinate system
|
||||
* @param options Options to use for the export
|
||||
* @param filePath Path to the file to export to
|
||||
*/
|
||||
public OBJExporterFactory(List<RocketComponent> components, FlightConfiguration configuration, boolean exportChildren, boolean triangulate,
|
||||
boolean removeOffset, ObjUtils.LevelOfDetail LOD, CoordTransform transformer, String filePath) {
|
||||
public OBJExporterFactory(List<RocketComponent> components, FlightConfiguration configuration, String filePath,
|
||||
OBJExportOptions options) {
|
||||
this.components = components;
|
||||
this.configuration = configuration;
|
||||
this.exportChildren = exportChildren;
|
||||
this.triangulate = triangulate;
|
||||
this.removeOffset = removeOffset;
|
||||
this.LOD = LOD;
|
||||
this.transformer = transformer;
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wavefront OBJ exporter.
|
||||
* @param components List of components to export
|
||||
* @param configuration Flight configuration to use for the export
|
||||
* @param exportChildren If true, export all children of the components as well
|
||||
* @param triangulate If true, triangulate all faces
|
||||
* @param removeOffset If true, remove the offset of the object so it is centered at the origin (but the bottom of the object is at y=0)
|
||||
* @param transformer Coordinate system transformer to use to switch from the OpenRocket coordinate system to a custom OBJ coordinate system
|
||||
* @param filePath Path to the file to export to
|
||||
*/
|
||||
public OBJExporterFactory(List<RocketComponent> components, FlightConfiguration configuration, boolean exportChildren, boolean triangulate,
|
||||
boolean removeOffset, CoordTransform transformer, String filePath) {
|
||||
this(components, configuration, exportChildren, triangulate, removeOffset, ObjUtils.LevelOfDetail.NORMAL, transformer, filePath);
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,9 +92,11 @@ public class OBJExporterFactory {
|
||||
*/
|
||||
public void doExport() {
|
||||
DefaultObj obj = new DefaultObj();
|
||||
boolean exportAsSeparateFiles = this.options.isExportAsSeparateFiles();
|
||||
|
||||
// Get all the components to export
|
||||
Set<RocketComponent> componentsToExport = new HashSet<>(this.components);
|
||||
if (this.exportChildren) {
|
||||
if (this.options.isExportChildren()) {
|
||||
for (RocketComponent component : this.components) {
|
||||
componentsToExport.addAll(component.getAllChildren());
|
||||
}
|
||||
@ -142,27 +118,44 @@ public class OBJExporterFactory {
|
||||
ArrayList<InstanceContext> contexts = map.get(component);
|
||||
contexts.get(0).transform.getXrotation();
|
||||
|
||||
// If separate export, create a new OBJ for each component
|
||||
if (exportAsSeparateFiles) {
|
||||
obj = new DefaultObj();
|
||||
}
|
||||
|
||||
// Component exporting
|
||||
String groupName = component.getName() + "_" + idx;
|
||||
handleComponent(obj, this.configuration, this.transformer, component, groupName, this.LOD);
|
||||
String groupName = idx + "_" + component.getName();
|
||||
handleComponent(obj, this.configuration, this.options.getTransformer(), component, groupName, this.options.getLOD());
|
||||
|
||||
// If separate export, already need to write the OBJ here
|
||||
if (exportAsSeparateFiles) {
|
||||
String path = FileUtils.removeExtension(this.filePath) + "_" + groupName + ".obj";
|
||||
writeObj(obj, path);
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (this.triangulate) {
|
||||
if (this.options.isTriangulate()) {
|
||||
obj = de.javagl.obj.ObjUtils.triangulate(obj, new DefaultObj());
|
||||
}
|
||||
|
||||
if (this.removeOffset) {
|
||||
if (this.options.isRemoveOffset()) {
|
||||
// Because of some rotation and translation operations when creating the meshes, the bounds can be inaccurate.
|
||||
// Therefore, we will recalculate them to be sure.
|
||||
// Is a bit computationally expensive, but it's the only way to be sure...
|
||||
obj.recalculateAllVertexBounds();
|
||||
|
||||
ObjUtils.removeVertexOffset(obj, transformer);
|
||||
ObjUtils.removeVertexOffset(obj, this.options.getTransformer());
|
||||
}
|
||||
|
||||
try (OutputStream objOutputStream = new FileOutputStream(this.filePath)) {
|
||||
if (!exportAsSeparateFiles) {
|
||||
writeObj(obj, this.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeObj(DefaultObj obj, String filePath) {
|
||||
try (OutputStream objOutputStream = new FileOutputStream(filePath)) {
|
||||
ObjWriter.write(obj, objOutputStream);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -38,4 +38,17 @@ public abstract class FileUtils {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the extension from a file name.
|
||||
* @param fileName the file name
|
||||
* @return the file name without extension
|
||||
*/
|
||||
public static String removeExtension(String fileName) {
|
||||
String[] splitResults = fileName.split("\\.");
|
||||
if (splitResults.length > 0) {
|
||||
return splitResults[0];
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,141 @@
|
||||
package net.sf.openrocket.file.wavefrontobj;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.file.wavefrontobj.export.OBJExportOptions;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentAssembly;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JToggleButton;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.List;
|
||||
|
||||
public class OBJOptionChooser extends JPanel {
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
// Widgets
|
||||
private final JCheckBox exportChildren;
|
||||
private final JCheckBox exportAppearance;
|
||||
private final JCheckBox exportAsSeparateFiles;
|
||||
private final JCheckBox removeOffset;
|
||||
private final JCheckBox triangulate;
|
||||
private final JComboBox<ObjUtils.LevelOfDetail> LOD;
|
||||
|
||||
public OBJOptionChooser(OBJExportOptions opts, List<RocketComponent> selectedComponents) {
|
||||
super(new MigLayout());
|
||||
|
||||
// ------------ Basic options ------------
|
||||
//// Export children
|
||||
this.exportChildren = new JCheckBox(trans.get("OBJOptionChooser.checkbox.exportChildren"));
|
||||
this.add(exportChildren, "spanx, wrap");
|
||||
|
||||
//// Export appearance
|
||||
this.exportAppearance = new JCheckBox(trans.get("OBJOptionChooser.checkbox.exportAppearance"));
|
||||
this.exportAppearance.setToolTipText(trans.get("OBJOptionChooser.checkbox.exportAppearance.ttip"));
|
||||
this.add(exportAppearance, "spanx, wrap");
|
||||
|
||||
//// Export as separate files
|
||||
this.exportAsSeparateFiles = new JCheckBox(trans.get("OBJOptionChooser.checkbox.exportAsSeparateFiles"));
|
||||
this.exportAsSeparateFiles.setToolTipText(trans.get("OBJOptionChooser.checkbox.exportAsSeparateFiles.ttip"));
|
||||
this.add(exportAsSeparateFiles, "spanx, wrap");
|
||||
|
||||
//// Remove offsets
|
||||
this.removeOffset = new JCheckBox(trans.get("OBJOptionChooser.checkbox.removeOffset"));
|
||||
this.removeOffset.setToolTipText(trans.get("OBJOptionChooser.checkbox.removeOffset.ttip"));
|
||||
this.add(removeOffset, "spanx, wrap para");
|
||||
|
||||
|
||||
// ------------ Advanced options ------------
|
||||
this.add(new JSeparator(JSeparator.HORIZONTAL), "spanx, growx, wrap");
|
||||
|
||||
// Show advanced options toggle
|
||||
JToggleButton advancedToggle = new JToggleButton(trans.get("OBJOptionChooser.btn.showAdvanced"));
|
||||
this.add(advancedToggle, "spanx, wrap para");
|
||||
|
||||
// Panel for advanced options
|
||||
JPanel advancedOptionsPanel = new JPanel();
|
||||
advancedOptionsPanel.setLayout(new MigLayout("ins 0"));
|
||||
advancedOptionsPanel.setVisible(false);
|
||||
|
||||
//// Triangulate
|
||||
this.triangulate = new JCheckBox(trans.get("OBJOptionChooser.checkbox.triangulate"));
|
||||
this.triangulate.setToolTipText(trans.get("OBJOptionChooser.checkbox.triangulate.ttip"));
|
||||
advancedOptionsPanel.add(triangulate, "spanx, wrap");
|
||||
|
||||
|
||||
//// Level of detail
|
||||
JLabel LODLabel = new JLabel(trans.get("OBJOptionChooser.lbl.LevelOfDetail"));
|
||||
LODLabel.setToolTipText(trans.get("OBJOptionChooser.lbl.LevelOfDetail.ttip"));
|
||||
advancedOptionsPanel.add(LODLabel);
|
||||
this.LOD = new JComboBox<>(ObjUtils.LevelOfDetail.values());
|
||||
this.LOD.setToolTipText(trans.get("OBJOptionChooser.lbl.LevelOfDetail.ttip"));
|
||||
advancedOptionsPanel.add(LOD, "spanx, wrap para");
|
||||
|
||||
|
||||
//// Scale
|
||||
// TODO: + (add tooltip text that mm is useful for 3D printing)
|
||||
|
||||
|
||||
//// Coordinate transformer
|
||||
// TODO
|
||||
|
||||
|
||||
// Add action listener to the toggle button
|
||||
advancedToggle.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
// Toggle the visibility of the advanced options panel
|
||||
advancedOptionsPanel.setVisible(e.getStateChange() == ItemEvent.SELECTED);
|
||||
|
||||
// Refresh the UI after changing visibility
|
||||
OBJOptionChooser.this.revalidate();
|
||||
OBJOptionChooser.this.repaint();
|
||||
}
|
||||
});
|
||||
this.add(advancedOptionsPanel);
|
||||
|
||||
loadOptions(opts, selectedComponents);
|
||||
}
|
||||
|
||||
public void loadOptions(OBJExportOptions opts, List<RocketComponent> selectedComponents) {
|
||||
boolean onlyComponentAssemblies = true;
|
||||
for (RocketComponent component : selectedComponents) {
|
||||
if (!(component instanceof ComponentAssembly)) {
|
||||
onlyComponentAssemblies = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (onlyComponentAssemblies) {
|
||||
exportChildren.setEnabled(false);
|
||||
exportChildren.setSelected(true);
|
||||
exportChildren.setToolTipText(trans.get("OBJOptionChooser.checkbox.exportChildren.assemblies.ttip"));
|
||||
} else {
|
||||
exportChildren.setEnabled(true);
|
||||
exportChildren.setSelected(opts.isExportChildren());
|
||||
exportChildren.setToolTipText(trans.get("OBJOptionChooser.checkbox.exportChildren.ttip"));
|
||||
}
|
||||
|
||||
this.exportAppearance.setSelected(opts.isExportAppearance());
|
||||
this.exportAsSeparateFiles.setSelected(opts.isExportAsSeparateFiles());
|
||||
this.removeOffset.setSelected(opts.isRemoveOffset());
|
||||
this.triangulate.setSelected(opts.isTriangulate());
|
||||
|
||||
this.LOD.setSelectedItem(opts.getLOD());
|
||||
}
|
||||
|
||||
public void storeOptions(OBJExportOptions opts) {
|
||||
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());
|
||||
}
|
||||
}
|
@ -51,6 +51,8 @@ import javax.swing.tree.TreeSelectionModel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
|
||||
import net.sf.openrocket.file.wavefrontobj.DefaultCoordTransform;
|
||||
import net.sf.openrocket.file.wavefrontobj.OBJOptionChooser;
|
||||
import net.sf.openrocket.file.wavefrontobj.export.OBJExportOptions;
|
||||
import net.sf.openrocket.file.wavefrontobj.export.OBJExporterFactory;
|
||||
import net.sf.openrocket.gui.configdialog.SaveDesignInfoPanel;
|
||||
import net.sf.openrocket.gui.dialogs.ErrorWarningDialog;
|
||||
@ -1387,10 +1389,15 @@ public class BasicFrame extends JFrame {
|
||||
/**
|
||||
* Opens a file chooser dialog for saving a new file, and returns the selected file.
|
||||
* @param fileType file type to use (e.g. RASAero)
|
||||
* @param selectedComponents list of selected components in the design
|
||||
* @return the file selected from the dialog, or null if no file was selected.
|
||||
*/
|
||||
private File openFileSaveAsDialog(FileType fileType) {
|
||||
final DesignFileSaveAsFileChooser chooser = DesignFileSaveAsFileChooser.build(document, fileType);
|
||||
private File openFileSaveAsDialog(FileType fileType, List<RocketComponent> selectedComponents) {
|
||||
final DesignFileSaveAsFileChooser chooser = DesignFileSaveAsFileChooser.build(document, fileType, selectedComponents);
|
||||
OBJOptionChooser objChooser = null;
|
||||
if (chooser.getAccessory() instanceof OBJOptionChooser) {
|
||||
objChooser = (OBJOptionChooser) chooser.getAccessory();
|
||||
}
|
||||
int option = chooser.showSaveDialog(BasicFrame.this);
|
||||
|
||||
if (option != JFileChooser.APPROVE_OPTION) {
|
||||
@ -1398,6 +1405,10 @@ public class BasicFrame extends JFrame {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (objChooser != null) {
|
||||
objChooser.storeOptions(document.getDefaultOBJOptions());
|
||||
}
|
||||
|
||||
File file = chooser.getSelectedFile();
|
||||
if (file == null) {
|
||||
log.info(Markers.USER_MARKER, "User did not select a file");
|
||||
@ -1409,6 +1420,15 @@ public class BasicFrame extends JFrame {
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file chooser dialog for saving a new file, and returns the selected file.
|
||||
* @param fileType file type to use (e.g. RASAero)
|
||||
* @return the file selected from the dialog, or null if no file was selected.
|
||||
*/
|
||||
private File openFileSaveAsDialog(FileType fileType) {
|
||||
return openFileSaveAsDialog(fileType, null);
|
||||
}
|
||||
|
||||
|
||||
//// BEGIN RASAero Save/Export Action
|
||||
/**
|
||||
@ -1608,13 +1628,42 @@ public class BasicFrame extends JFrame {
|
||||
|
||||
|
||||
//// BEGIN WAVEFRONT OBJ Save/Export Action
|
||||
private void exportWavefrontOBJAction() {
|
||||
// TODO: popup dialog for extra options (quality, whether to triangulate, whether to export materials, whether to save all subcomponents of the selected ones, whether to offset the object position to zero or to the location in the rocket, whether to save the rocket dimensions in SI units or in mm (add tooltip text that mm is useful for 3D printing) etc.)
|
||||
String filePath = "/Users/SiboVanGool/Downloads/test.obj";
|
||||
CoordTransform transformer = new DefaultCoordTransform(rocket.getLength());
|
||||
/**
|
||||
* MODEL "Export as" Wavefront OBJ file format
|
||||
*
|
||||
* @return true if the file was saved, false otherwise
|
||||
*/
|
||||
private boolean exportWavefrontOBJAction() {
|
||||
File file = openFileSaveAsDialog(FileType.WAVEFRONT_OBJ, getSelectedComponents());
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file = FileHelper.forceExtension(file, "obj");
|
||||
boolean isExportAsSeparateFiles = document.getDefaultOBJOptions().isExportAsSeparateFiles();
|
||||
if (isExportAsSeparateFiles || FileHelper.confirmWrite(file, BasicFrame.this)) { // No overwrite warning for separate files
|
||||
return saveAsWavefrontOBJ(file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean saveAsWavefrontOBJ(File file) {
|
||||
OBJExportOptions options = document.getDefaultOBJOptions();
|
||||
return saveWavefrontOBJFile(file, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual saving of the Wavefront OBJ file
|
||||
* @param file file to be stored
|
||||
* @param options OBJ export options to use
|
||||
* @return true if the file was written
|
||||
*/
|
||||
private boolean saveWavefrontOBJFile(File file, OBJExportOptions options) {
|
||||
OBJExporterFactory exporter = new OBJExporterFactory(getSelectedComponents(), rocket.getSelectedConfiguration(),
|
||||
false, false, true, transformer, filePath);
|
||||
file.getAbsolutePath(), options);
|
||||
exporter.doExport();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,6 +3,7 @@ package net.sf.openrocket.gui.main;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
@ -10,12 +11,15 @@ 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.gui.util.FileHelper;
|
||||
import net.sf.openrocket.gui.util.SimpleFileFilter;
|
||||
import net.sf.openrocket.gui.util.SwingPreferences;
|
||||
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.util.FileUtils;
|
||||
|
||||
public class DesignFileSaveAsFileChooser extends SaveFileChooser {
|
||||
|
||||
@ -24,11 +28,15 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser {
|
||||
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
public static DesignFileSaveAsFileChooser build(OpenRocketDocument document, FileType type ) {
|
||||
return new DesignFileSaveAsFileChooser(document,type);
|
||||
public static DesignFileSaveAsFileChooser build(OpenRocketDocument document, FileType type) {
|
||||
return new DesignFileSaveAsFileChooser(document, type, null);
|
||||
}
|
||||
|
||||
private DesignFileSaveAsFileChooser(OpenRocketDocument document, FileType type ) {
|
||||
public static DesignFileSaveAsFileChooser build(OpenRocketDocument document, FileType type, List<RocketComponent> selectedComponents) {
|
||||
return new DesignFileSaveAsFileChooser(document, type, selectedComponents);
|
||||
}
|
||||
|
||||
private DesignFileSaveAsFileChooser(OpenRocketDocument document, FileType type, List<RocketComponent> selectedComponents) {
|
||||
this.document = document;
|
||||
this.type = type;
|
||||
|
||||
@ -58,6 +66,14 @@ public class DesignFileSaveAsFileChooser extends SaveFileChooser {
|
||||
this.addChoosableFileFilter(FileHelper.RASAERO_DESIGN_FILTER);
|
||||
this.setFileFilter(FileHelper.RASAERO_DESIGN_FILTER);
|
||||
break;
|
||||
case WAVEFRONT_OBJ:
|
||||
defaultFilename = FileHelper.forceExtension(defaultFilename,"obj");
|
||||
this.setDialogTitle(trans.get("saveAs.wavefront.title"));
|
||||
OBJOptionChooser objChooser = new OBJOptionChooser(document.getDefaultOBJOptions(), selectedComponents);
|
||||
this.setAccessory(objChooser);
|
||||
this.addChoosableFileFilter(FileHelper.WAVEFRONT_OBJ_FILTER);
|
||||
this.setFileFilter(FileHelper.WAVEFRONT_OBJ_FILTER);
|
||||
break;
|
||||
}
|
||||
|
||||
final RememberFilenamePropertyListener listener = new RememberFilenamePropertyListener();
|
||||
@ -108,19 +124,11 @@ class RememberFilenamePropertyListener implements PropertyChangeListener {
|
||||
String currentFileName = oldFileName;
|
||||
|
||||
if (!filter.accept(new File(currentFileName))) {
|
||||
currentFileName = removeExtension(currentFileName);
|
||||
currentFileName = FileUtils.removeExtension(currentFileName);
|
||||
chooser.setSelectedFile(new File(currentFileName + desiredExtension));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String removeExtension(String fileName) {
|
||||
String[] splitResults = fileName.split("\\.");
|
||||
if (splitResults.length > 0) {
|
||||
return splitResults[0];
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -48,6 +48,10 @@ public final class FileHelper {
|
||||
public static final FileFilter RASAERO_DESIGN_FILTER =
|
||||
new SimpleFileFilter(trans.get("FileHelper.RASAERO_DESIGN_FILTER"), ".CDX1", ".CDX1.gz");
|
||||
|
||||
/** File filter for RASAero II designs (*.CDX1) */
|
||||
public static final FileFilter WAVEFRONT_OBJ_FILTER =
|
||||
new SimpleFileFilter(trans.get("FileHelper.WAVEFRONT_OBJ_FILTER"), ".obj");
|
||||
|
||||
/** File filter for OpenRocket components and presets (*.orc) */
|
||||
public static final FileFilter OPEN_ROCKET_COMPONENT_FILTER =
|
||||
new SimpleFileFilter(trans.get("FileHelper.OPEN_ROCKET_COMPONENT_FILTER"), ".orc", ".orc.gz");
|
||||
|
Loading…
x
Reference in New Issue
Block a user