From cca0ec4a245530a0764b46dda481fffa2f9d3c06 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 14 Feb 2024 13:19:18 +0100 Subject: [PATCH] Add triangulation method setting to OBJ export dialog --- core/resources/l10n/messages.properties | 8 ++++ .../file/wavefrontobj/ObjUtils.java | 37 +++++++++++++++++++ .../wavefrontobj/export/OBJExportOptions.java | 12 ++++++ .../export/OBJExporterFactory.java | 9 ++++- .../sf/openrocket/startup/Preferences.java | 5 +++ .../export/OBJExporterFactoryTest.java | 9 +++++ .../file/wavefrontobj/OBJOptionChooser.java | 35 ++++++++++++++++++ 7 files changed, 114 insertions(+), 1 deletion(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 9225aff7d..b51057a5d 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1556,6 +1556,8 @@ OBJOptionChooser.checkbox.removeOffset.ttip = If true, remove the offset o 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.triangulationMethod = Triangulation method: +OBJOptionChooser.lbl.triangulationMethod.ttip = Select the desired algorithm to use for the triangulation. OBJOptionChooser.checkbox.sRGB = Export colors in sRGB OBJOptionChooser.checkbox.sRGB.ttip = If true, export colors in sRGB instead of a linear color scheme.
Is useful for instance when exporting for use in Blender. OBJOptionChooser.lbl.Scaling = Scaling: @@ -1574,6 +1576,12 @@ LevelOfDetail.LOW_QUALITY = Low quality LevelOfDetail.NORMAL_QUALITY = Normal quality LevelOfDetail.HIGH_QUALITY = High quality +! TriangulationMethod +TriangulationMethod.SIMPLE = Simple (fast) +TriangulationMethod.SIMPLE.ttip = Simple triangulation method (fast, but may produce poor results) +TriangulationMethod.DELAUNAY = Delaunay (recommended) +TriangulationMethod.DELAUNAY.ttip = Constrained Delaunay triangulation method (recommended, but may be slow for large models) + ! ThrustCurveMotorSelectionPanel TCMotorSelPan.lbl.Selrocketmotor = Select rocket motor: TCMotorSelPan.checkbox.hideSimilar = Hide very similar thrust curves diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java b/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java index 8f3446b0c..8c9f03ce0 100644 --- a/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java +++ b/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java @@ -76,6 +76,43 @@ public class ObjUtils { } } + public enum TriangulationMethod { + SIMPLE(trans.get("TriangulationMethod.SIMPLE"), trans.get("TriangulationMethod.SIMPLE.ttip"), "SIMPLE"), + DELAUNAY(trans.get("TriangulationMethod.DELAUNAY"), trans.get("TriangulationMethod.DELAUNAY.ttip"), "DELAUNAY"); + + private final String label; + private final String tooltip; + private final String exportLabel; + + TriangulationMethod(String label, String tooltip, String exportLabel) { + this.label = label; + this.tooltip = tooltip; + this.exportLabel = exportLabel; + } + + @Override + public String toString() { + return label; + } + + public String getTooltip() { + return tooltip; + } + + public String getExportLabel() { + return exportLabel; + } + + public static TriangulationMethod fromExportLabel(String exportLabel) { + for (TriangulationMethod tm : TriangulationMethod.values()) { + if (tm.getExportLabel().equals(exportLabel)) { + return tm; + } + } + return TriangulationMethod.DELAUNAY; + } + } + /** * Offset the indices by the given offset diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExportOptions.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExportOptions.java index e89b108c2..1a987dd6d 100644 --- a/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExportOptions.java +++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExportOptions.java @@ -33,6 +33,10 @@ public class OBJExportOptions { * If true, triangulate all faces (convert quads and higher-order polygons to triangles) */ private boolean triangulate; + /** + * The method to use for triangulation. + */ + private ObjUtils.TriangulationMethod triangulationMethod; /** * If true, use sRGB colors instead of linear color space. */ @@ -94,6 +98,14 @@ public class OBJExportOptions { this.triangulate = triangulate; } + public ObjUtils.TriangulationMethod getTriangulationMethod() { + return triangulationMethod; + } + + public void setTriangulationMethod(ObjUtils.TriangulationMethod triangulationMethod) { + this.triangulationMethod = triangulationMethod; + } + public boolean isExportAppearance() { return exportAppearance; } diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java index cbfb760e1..b11f90f5d 100644 --- a/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java +++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java @@ -175,7 +175,14 @@ public class OBJExporterFactory { // Triangulate mesh if (this.options.isTriangulate()) { - obj = TriangulationHelper.constrainedDelaunayTriangulate(obj); + ObjUtils.TriangulationMethod triangulationMethod = this.options.getTriangulationMethod(); + if (triangulationMethod == ObjUtils.TriangulationMethod.DELAUNAY) { + obj = TriangulationHelper.constrainedDelaunayTriangulate(obj); + } else if (triangulationMethod == ObjUtils.TriangulationMethod.SIMPLE) { + obj = TriangulationHelper.simpleTriangulate(obj); + } else { + throw new IllegalArgumentException("Unsupported triangulation method: " + triangulationMethod); + } } // Remove position offset diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java index 17d70440e..30a3e1822 100644 --- a/core/src/net/sf/openrocket/startup/Preferences.java +++ b/core/src/net/sf/openrocket/startup/Preferences.java @@ -128,6 +128,7 @@ public abstract class Preferences implements ChangeSource { 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_TRIANGULATION_METHOD = "TriangulationMethod"; private static final String OBJ_SRGB = "sRGB"; private static final String OBJ_LOD = "LOD"; private static final String OBJ_SCALING = "Scaling"; @@ -1051,6 +1052,7 @@ public abstract class Preferences implements ChangeSource { objExportOptionsNode.putBoolean(OBJ_EXPORT_AS_SEPARATE_FILES, options.isExportAsSeparateFiles()); objExportOptionsNode.putBoolean(OBJ_REMOVE_OFFSET, options.isRemoveOffset()); objExportOptionsNode.putBoolean(OBJ_TRIANGULATE, options.isTriangulate()); + objExportOptionsNode.put(OBJ_TRIANGULATION_METHOD, options.getTriangulationMethod().getExportLabel()); objExportOptionsNode.putBoolean(OBJ_SRGB, options.isUseSRGB()); objExportOptionsNode.putFloat(OBJ_SCALING, options.getScaling()); @@ -1081,6 +1083,9 @@ public abstract class Preferences implements ChangeSource { options.setExportAsSeparateFiles(objExportOptionsNode.getBoolean(OBJ_EXPORT_AS_SEPARATE_FILES, false)); options.setRemoveOffset(objExportOptionsNode.getBoolean(OBJ_REMOVE_OFFSET, true)); options.setTriangulate(objExportOptionsNode.getBoolean(OBJ_TRIANGULATE, true)); + options.setTriangulationMethod(ObjUtils.TriangulationMethod.fromExportLabel( + objExportOptionsNode.get(OBJ_TRIANGULATION_METHOD, ObjUtils.TriangulationMethod.DELAUNAY.getExportLabel()) + )); options.setUseSRGB(objExportOptionsNode.getBoolean(OBJ_SRGB, false)); options.setScaling(objExportOptionsNode.getFloat(OBJ_SCALING, 1000)); diff --git a/core/test/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactoryTest.java b/core/test/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactoryTest.java index af5f5dd5c..769be8698 100644 --- a/core/test/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactoryTest.java +++ b/core/test/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactoryTest.java @@ -175,6 +175,7 @@ public class OBJExporterFactoryTest { bodyTube.setFilled(true); options.setTriangulate(true); + options.setTriangulationMethod(ObjUtils.TriangulationMethod.DELAUNAY); options.setRemoveOffset(false); options.setExportAppearance(true); options.setScaling(1000); @@ -193,6 +194,14 @@ public class OBJExporterFactoryTest { //// Just hope for no exceptions :) assertEquals(warnings.size(), 1); + // Test simple triangulation + options.setTriangulationMethod(ObjUtils.TriangulationMethod.SIMPLE); + + exporterFactory = new OBJExporterFactory(components, rocket.getSelectedConfiguration(), tempFile.toFile(), options, warnings); + exporterFactory.doExport(); + //// Just hope for no exceptions :) + assertEquals(warnings.size(), 1); + // Clean up Files.delete(tempFile); } diff --git a/swing/src/net/sf/openrocket/file/wavefrontobj/OBJOptionChooser.java b/swing/src/net/sf/openrocket/file/wavefrontobj/OBJOptionChooser.java index ec42ad0a0..912dcd0f0 100644 --- a/swing/src/net/sf/openrocket/file/wavefrontobj/OBJOptionChooser.java +++ b/swing/src/net/sf/openrocket/file/wavefrontobj/OBJOptionChooser.java @@ -15,11 +15,13 @@ import net.sf.openrocket.unit.UnitGroup; import javax.swing.AbstractButton; import javax.swing.BorderFactory; +import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; +import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSeparator; @@ -29,6 +31,7 @@ import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.Color; +import java.awt.Component; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -53,6 +56,7 @@ public class OBJOptionChooser extends JPanel { private final JCheckBox exportAsSeparateFiles; private final JCheckBox removeOffset; private final JCheckBox triangulate; + private final JComboBox triangulationMethod; private final JCheckBox sRGB; private final JComboBox LOD; private final DoubleModel scalingModel; @@ -236,6 +240,17 @@ public class OBJOptionChooser extends JPanel { } }); + //// Triangulation method + JLabel tmLabel = new JLabel(trans.get("OBJOptionChooser.lbl.triangulationMethod")); + tmLabel.setToolTipText(trans.get("OBJOptionChooser.lbl.triangulationMethod.ttip")); + advancedOptionsPanel.add(tmLabel, "spanx, split 2"); + this.triangulationMethod = new JComboBox<>(ObjUtils.TriangulationMethod.values()); + this.triangulationMethod.setToolTipText(trans.get("OBJOptionChooser.lbl.triangulationMethod.ttip")); + this.triangulationMethod.setRenderer(new TriangulationMethodRenderer()); + destroyTheMagic(triangulationMethod); + addOptimizationListener(triangulationMethod); + advancedOptionsPanel.add(triangulationMethod, "growx, wrap unrel"); + //// Level of detail JLabel LODLabel = new JLabel(trans.get("OBJOptionChooser.lbl.LevelOfDetail")); LODLabel.setToolTipText(trans.get("OBJOptionChooser.lbl.LevelOfDetail.ttip")); @@ -394,6 +409,7 @@ public class OBJOptionChooser extends JPanel { if (!opts.isTriangulate()) { this.exportAppearance.setSelected(opts.isExportAppearance()); } + this.triangulationMethod.setSelectedItem(opts.getTriangulationMethod()); this.sRGB.setSelected(opts.isUseSRGB()); this.scalingModel.setValue(opts.getScaling()); @@ -422,6 +438,7 @@ public class OBJOptionChooser extends JPanel { opts.setExportAsSeparateFiles(exportAsSeparateFiles.isSelected()); opts.setRemoveOffset(removeOffset.isSelected()); opts.setTriangulate(triangulate.isSelected()); + opts.setTriangulationMethod((ObjUtils.TriangulationMethod) triangulationMethod.getSelectedItem()); opts.setUseSRGB(sRGB.isSelected()); opts.setScaling((float) scalingModel.getValue()); opts.setLOD((ObjUtils.LevelOfDetail) LOD.getSelectedItem()); @@ -441,6 +458,7 @@ public class OBJOptionChooser extends JPanel { options.setRemoveOffset(true); options.setScaling(1000); options.setTriangulate(true); + options.setTriangulationMethod(ObjUtils.TriangulationMethod.DELAUNAY); options.setLOD(ObjUtils.LevelOfDetail.HIGH_QUALITY); loadOptions(options); @@ -453,6 +471,7 @@ public class OBJOptionChooser extends JPanel { */ private boolean isOptimizedFor3DPrinting(OBJExportOptions options) { return !options.isExportMotors() && !options.isExportAppearance() && options.isTriangulate() && + options.getTriangulationMethod() == ObjUtils.TriangulationMethod.DELAUNAY && options.getLOD() == ObjUtils.LevelOfDetail.HIGH_QUALITY && options.isRemoveOffset() && options.getScaling() == 1000; } @@ -581,6 +600,22 @@ public class OBJOptionChooser extends JPanel { } } + private static class TriangulationMethodRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + + JComponent comp = (JComponent) super.getListCellRendererComponent(list, + value, index, isSelected, cellHasFocus); + + if (index > -1 && value instanceof ObjUtils.TriangulationMethod) { + list.setToolTipText(((ObjUtils.TriangulationMethod) value).getTooltip()); + } + return comp; + } + } + /*private void coordTransComboAction(ItemEvent e, JComboBox otherCombo) { if (e.getStateChange() != ItemEvent.SELECTED) { return;