From 4f9b19b1b6cfeafd9e3cf06fa2a4642766738694 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Wed, 4 Mar 2026 23:12:57 -0500 Subject: [PATCH 01/44] add the new library to the CI --- build.gradle | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 37fe267e..1f17cad4 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,7 @@ task sourcesJar(type: Jar) { repositories { mavenCentral() mavenLocal() + maven { url "https://clojars.org/repo" } } // javadoc is way too strict for my taste. @@ -119,8 +120,11 @@ dependencies { implementation 'com.aparapi:aparapi:3.0.2' //SSL for server -implementation 'org.bouncycastle:bcprov-jdk18on:1.80' -implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' + implementation 'org.bouncycastle:bcprov-jdk18on:1.80' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' + + //manifold 3d + implementation("com.github.madhephaestus:manifold3d:2.1.0") } From 808d22336a1813cfaf5ee2e6bcde52c959c36c30 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Thu, 5 Mar 2026 14:06:14 -0500 Subject: [PATCH 02/44] basic build test --- build.gradle | 2 +- .../java/eu/mihosoft/vrl/v3d/Manifold3d_test.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java diff --git a/build.gradle b/build.gradle index 1f17cad4..eb2b7411 100644 --- a/build.gradle +++ b/build.gradle @@ -124,7 +124,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:2.1.0") + implementation("com.github.madhephaestus:manifold3d:3.0.2") } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java new file mode 100644 index 00000000..66f88ed2 --- /dev/null +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -0,0 +1,11 @@ +package eu.mihosoft.vrl.v3d; + +import org.junit.Test; +import manifold3d.Manifold; + +public class Manifold3d_test { + @Test + public void loadTest() { + Manifold sphere = Manifold3d.Sphere(10.0f, 20); + } +} From 58665dcf0310d6bdb59134dd100948396303c0df Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Thu, 5 Mar 2026 14:19:57 -0500 Subject: [PATCH 03/44] updating to the latest version from clojar --- build.gradle | 2 +- src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index eb2b7411..706bd886 100644 --- a/build.gradle +++ b/build.gradle @@ -124,7 +124,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.0.2") + implementation("com.github.madhephaestus:manifold3d:3.0.4") } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index 66f88ed2..e826660e 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -6,6 +6,6 @@ public class Manifold3d_test { @Test public void loadTest() { - Manifold sphere = Manifold3d.Sphere(10.0f, 20); + Manifold sphere = Manifold.Sphere(10.0f, 20); } } From b83ee368ec04fb4bfaf34b327bb4759715d04eea Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 6 Mar 2026 09:25:41 -0500 Subject: [PATCH 04/44] update tbb dep handeling --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 706bd886..ee18db02 100644 --- a/build.gradle +++ b/build.gradle @@ -124,7 +124,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.0.4") + implementation("com.github.madhephaestus:manifold3d:3.0.7") } From 50117bdd9ace15e126b899eb3327920f622db976 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sun, 15 Mar 2026 10:57:27 -0400 Subject: [PATCH 05/44] update Manifold to use the bindings --- build.gradle | 53 ++++++------------- .../eu/mihosoft/vrl/v3d/Manifold3d_test.java | 8 +-- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index ee18db02..a4212ff9 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,10 @@ plugins { id 'signing' //id 'io.codearte.nexus-staging' version '0.30.0' } - +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} File buildDir = file("."); Properties props = new Properties() props.load(new FileInputStream(buildDir.getAbsolutePath()+"/src/main/resources/com/neuronrobotics/javacad/build.properties")) @@ -14,41 +17,7 @@ group = "com.neuronrobotics" archivesBaseName = "JavaCad" version = props."app.version" -// BEGIN AI SLOP - -//nexusStaging { -// serverUrl = "https://oss.sonatype.org/service/local/" -// username = System.getenv("MAVEN_USERNAME") -// password = System.getenv("MAVEN_PASSWORD") -// packageGroup = "com.neuronrobotics" // Replace with your actual package group -//} - -//task closeAndReleaseSeparately { -// dependsOn tasks.releaseRepository -//} - -//tasks.releaseRepository.dependsOn tasks.closeRepository -//tasks.closeRepository.dependsOn tasks.getStagingProfile - -// Optional: Add this if you want to see more information during the execution -//tasks.getStagingProfile.logging.level = LogLevel.INFO -//tasks.closeRepository.logging.level = LogLevel.INFO -//tasks.releaseRepository.logging.level = LogLevel.INFO - -//tasks.getStagingProfile.doFirst { -// println "Executing getStagingProfile task" -//} - -//tasks.closeRepository.doFirst { -// println "Executing closeRepository task" -//} -// -//tasks.releaseRepository.doFirst { -// println "Executing releaseRepository task" -//} - -// END AI SLOP -sourceCompatibility = '1.8' + [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' //apply from: 'http://gradle-plugins.mihosoft.eu/latest/vlicenseheader.gradle' @@ -124,7 +93,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.0.7") + implementation("com.github.madhephaestus:manifold3d:3.2.1") } @@ -134,7 +103,17 @@ ext { buildTime = new java.text.SimpleDateFormat('HH:mm:ss.SSSZ').format(buildTimeAndDate) } +tasks.withType(JavaCompile).configureEach { + options.compilerArgs += ['--enable-preview'] +} + +tasks.withType(Test).configureEach { + jvmArgs '--enable-preview' +} +tasks.withType(JavaExec).configureEach { + jvmArgs '--enable-preview' +} test { testLogging { // Show which test is running diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index e826660e..4e8fcd76 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -1,11 +1,13 @@ package eu.mihosoft.vrl.v3d; import org.junit.Test; -import manifold3d.Manifold; + +import com.cadoodlecad.manifold.ManifoldBindings; public class Manifold3d_test { @Test - public void loadTest() { - Manifold sphere = Manifold.Sphere(10.0f, 20); + public void loadTest() throws Exception { + ManifoldBindings manifold = new ManifoldBindings(); + } } From 188cfaae209f1069e331d07e92e8e70383208a71 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sun, 15 Mar 2026 12:23:36 -0400 Subject: [PATCH 06/44] cleanup --- .github/workflows/verify.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 1721770f..582eb55f 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -42,9 +42,6 @@ jobs: - name: Pull a JavaFX JDK run: wget https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-fx-jdk21.0.9-linux_aarch64.tar.gz - - name: After JDK download, list directory contents - run: pwd; ls -la - - name: Set Java uses: actions/setup-java@v1 with: @@ -119,9 +116,6 @@ jobs: - name: Pull a JavaFX JDK run: wget https://cdn.azul.com/zulu/bin/zulu21.46.19-ca-fx-jdk21.0.9-linux_x64.tar.gz - - name: After JDK download, list directory contnts - run: pwd; ls -la - - name: Set Java uses: actions/setup-java@v1 with: From e31d949359c5900eabea594c4596afe358d7b5c5 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sun, 15 Mar 2026 14:07:30 -0400 Subject: [PATCH 07/44] Should pass all archetectures --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a4212ff9..7b1af409 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.2.1") + implementation("com.github.madhephaestus:manifold3d:3.2.3-99015273-SNAPSHOT") } From ec851c9212749db72452ac7b842d111b15eea3e5 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sun, 15 Mar 2026 14:46:40 -0400 Subject: [PATCH 08/44] Update the snapshot to include the java fix for arm platforms --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7b1af409..239a0982 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.2.3-99015273-SNAPSHOT") + implementation("com.github.madhephaestus:manifold3d:3.2.3-89588463-SNAPSHOT") } From ab8047173da063dce5d1d261ea20bb808cafd98b Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sun, 15 Mar 2026 14:57:27 -0400 Subject: [PATCH 09/44] use the first version built entirely in CI --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 239a0982..03c17f31 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.2.3-89588463-SNAPSHOT") + implementation("com.github.madhephaestus:manifold3d:3.2.4") } From 18d74cf8f04ea84f6eacae0b7ff97536cb6bf4f8 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sun, 15 Mar 2026 15:32:59 -0400 Subject: [PATCH 10/44] Updating to the ci build lib name standard --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 03c17f31..eb81fab7 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.2.4") + implementation("com.github.madhephaestus:manifold3d:3.2.7") } From f973d01da1389eb98465ad9c452fa8ad1255bfcb Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sun, 15 Mar 2026 15:50:53 -0400 Subject: [PATCH 11/44] update to the new windows build version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index eb81fab7..5b7962a2 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.2.7") + implementation("com.github.madhephaestus:manifold3d:3.2.11") } From c9736d34955f1ac3e6a067ab9e176bf2cac45ca5 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sun, 15 Mar 2026 17:41:21 -0400 Subject: [PATCH 12/44] updaing to the lates CI build release --- build.gradle | 2 +- src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 5b7962a2..73aaec6f 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.2.11") + implementation("com.github.madhephaestus:manifold3d:3.2.16") } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index 4e8fcd76..7c94fc05 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -1,13 +1,15 @@ package eu.mihosoft.vrl.v3d; +import java.lang.foreign.MemorySegment; + import org.junit.Test; import com.cadoodlecad.manifold.ManifoldBindings; public class Manifold3d_test { @Test - public void loadTest() throws Exception { + public void loadTest() throws Throwable { ManifoldBindings manifold = new ManifoldBindings(); - + MemorySegment cube = manifold.cube(10, 10, 10, false); } } From 9e9a784ff0cd3862e51b66bce5cbc136d3c9fb26 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 10:38:10 -0400 Subject: [PATCH 13/44] set up the option type for JCSG --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 14 ++++++++++++++ .../java/eu/mihosoft/vrl/v3d/Manifold3d_test.java | 6 ++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index fc5cdf70..46898c5d 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -69,6 +69,7 @@ import com.aparapi.device.Device; import com.aparapi.internal.kernel.KernelManager; import com.aparapi.internal.kernel.KernelRunner; +import com.cadoodlecad.manifold.ManifoldBindings; import com.neuronrobotics.interaction.CadInteractionEvent; import javafx.scene.paint.Color; @@ -199,6 +200,7 @@ public void progressUpdate(int currentIndex, int finalIndex, String type, CSG in private int pointsAdded; private String uniqueId = UUID.randomUUID().toString(); + private static ManifoldBindings manifold=null; /** * Instantiates a new csg. @@ -2715,6 +2717,15 @@ protected OptType getOptType() { * @param optType the optType to set */ public static void setDefaultOptType(OptType optType) { + if(optType == OptType.Manifold3d) { + try { + manifold = new ManifoldBindings(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + optType=defaultOptType; + } + } defaultOptType = optType; } @@ -2724,6 +2735,7 @@ public static void setDefaultOptType(OptType optType) { * @param optType the optType to set */ public CSG setOptType(OptType optType) { + this.optType = optType; return this; } @@ -2750,6 +2762,8 @@ public static enum OptType { /** The polygon bound. */ POLYGON_BOUND, + + Manifold3d, /** The none. */ NONE diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index 7c94fc05..08332827 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -6,10 +6,12 @@ import com.cadoodlecad.manifold.ManifoldBindings; +import eu.mihosoft.vrl.v3d.CSG.OptType; + public class Manifold3d_test { @Test public void loadTest() throws Throwable { - ManifoldBindings manifold = new ManifoldBindings(); - MemorySegment cube = manifold.cube(10, 10, 10, false); + CSG.setDefaultOptType(OptType.Manifold3d); + } } From aba1e332a56d880dc0a2f71297c1084a95728689 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 10:58:47 -0400 Subject: [PATCH 14/44] add all of the stubs for basic operations --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 22 ++++++++++++++++++- .../vrl/v3d/ext/quickhull3d/HullUtil.java | 4 ++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 46898c5d..e3161a16 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -870,6 +870,8 @@ public CSG union(CSG csg) { return _unionCSGBoundsOpt(csg).historySync(this).historySync(csg); case POLYGON_BOUND: return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); + case Manifold3d: + new RuntimeException("Not implemented yet").printStackTrace(); default: // return _unionIntersectOpt(csg); return _unionNoOpt(csg).historySync(this).historySync(csg); @@ -1381,13 +1383,15 @@ public CSG difference(CSG csg) { return _differenceCSGBoundsOpt(csg).historySync(this).historySync(csg); case POLYGON_BOUND: return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); + case Manifold3d: + new RuntimeException("Not implemented yet").printStackTrace(); default: return _differenceNoOpt(csg).historySync(this).historySync(csg); } } else return this; } catch (Exception ex) { - // ex.printStackTrace(); + ex.printStackTrace(); try { // com.neuronrobotics.sdk.common.Log.error("CSG difference failed, performing // workaround"); @@ -1402,6 +1406,8 @@ public CSG difference(CSG csg) { case POLYGON_BOUND: return _differencePolygonBoundsOpt(intersectingParts).historySync(this) .historySync(intersectingParts); + case Manifold3d: + new RuntimeException("Not implemented yet").printStackTrace(); default: return _differenceNoOpt(intersectingParts).historySync(this).historySync(intersectingParts); } @@ -2720,6 +2726,15 @@ public static void setDefaultOptType(OptType optType) { if(optType == OptType.Manifold3d) { try { manifold = new ManifoldBindings(); + Slice.setSliceEngine(new ISlice() { + + @Override + public List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) + throws ColinearPointsException { + new RuntimeException("Manifold3d not implemented yet").printStackTrace(); + return new ArrayList(); + } + }); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -4152,4 +4167,9 @@ public String getUniqueId() { return uniqueId; } + public static OptType getDefaultOptionType() { + // TODO Auto-generated method stub + return defaultOptType; + } + } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java index 5d886cf6..430930be 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java @@ -6,6 +6,7 @@ package eu.mihosoft.vrl.v3d.ext.quickhull3d; import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.CSG.OptType; import eu.mihosoft.vrl.v3d.CSGClient; import eu.mihosoft.vrl.v3d.ColinearPointsException; import eu.mihosoft.vrl.v3d.Vector3d; @@ -71,6 +72,9 @@ public static CSG hull(List points, PropertyStorage storage) { e.printStackTrace(); } } + if(CSG.getDefaultOptionType()==OptType.Manifold3d) { + new RuntimeException("Not implemented yet").printStackTrace(); + } Point3d[] hullPoints = points.stream().map((vec) -> new Point3d(vec.x, vec.y, vec.z)).toArray(Point3d[]::new); QuickHull3D hull = new QuickHull3D(); From a6ab6df96ae2e88094a26fb9f91447f798e0f003 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 11:38:41 -0400 Subject: [PATCH 15/44] adding the stubs for the STL loading/export path --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 3 +++ src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index e3161a16..37002d53 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -1682,6 +1682,9 @@ public String toStlString() { * @return the specified string builder */ public StringBuilder toStlString(StringBuilder sb) { + if(defaultOptType == OptType.Manifold3d) { + new RuntimeException("Manifold3d STL export not implemented yet").printStackTrace(); + } triangulate(false); try { sb.append("solid v3d.csg\n"); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java index a8f6a142..392074e2 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.List; +import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.CSG.OptType; import eu.mihosoft.vrl.v3d.ColinearPointsException; import eu.mihosoft.vrl.v3d.Plane; import eu.mihosoft.vrl.v3d.Polygon; @@ -50,6 +52,9 @@ public STLLoader() { * @throws IOException Signals that an I/O exception has occurred. */ public ArrayList parse(File f) throws IOException { + if(CSG.getDefaultOptionType()==OptType.Manifold3d) { + new RuntimeException("Manifold3d STL import not implemented yet").printStackTrace(); + } ArrayList polygons = new ArrayList<>(); // determine if this is a binary or ASCII STL From 8dccebcbca80c795267e14003e2602034263bdb3 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 11:43:09 -0400 Subject: [PATCH 16/44] Adding stubs for 3mf loader/export --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 37002d53..b2324601 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -42,6 +42,7 @@ import eu.mihosoft.vrl.v3d.parametrics.LengthParameter; import eu.mihosoft.vrl.v3d.parametrics.Parameter; +import java.io.File; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Field; @@ -1673,7 +1674,23 @@ public String toStlString() { toStlString(sb); return sb.toString(); } - + + public CSG to3mf(File target) { + if(defaultOptType == OptType.Manifold3d) { + new RuntimeException("Manifold3d 3mf export not implemented yet").printStackTrace(); + }else { + throw new RuntimeException("Non-Manifold3d 3mf export not implemented yet"); + } + return this; + } + public static CSG loadFrom3mf(File target) { + if(defaultOptType == OptType.Manifold3d) { + new RuntimeException("Manifold3d 3mf export not implemented yet").printStackTrace(); + }else { + throw new RuntimeException("Non-Manifold3d 3mf export not implemented yet"); + } + return null; + } /** * Returns this csg in STL string format. * From 954491a4278467967ecabf6ccea35ff0544b1527 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 11:48:59 -0400 Subject: [PATCH 17/44] make sure the test leaves the operation in standard statee --- .../java/eu/mihosoft/vrl/v3d/Manifold3d_test.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index 08332827..96127e21 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -11,7 +11,18 @@ public class Manifold3d_test { @Test public void loadTest() throws Throwable { - CSG.setDefaultOptType(OptType.Manifold3d); - + OptType og = CSG.getDefaultOptionType(); + + try { + CSG.setDefaultOptType(OptType.Manifold3d); + + } catch (Throwable t) { + t.printStackTrace(); + // Set back to default to complete test and not disrupt other tests + CSG.setDefaultOptType(og); + throw t; + } + // Set back to default to complete test and not disrupt other tests + CSG.setDefaultOptType(og); } } From fc38693fa03bfcfd6c025b47efdcce90b8313e07 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 11:59:14 -0400 Subject: [PATCH 18/44] adding the intersect variant to manifold --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index b2324601..bfbd9b63 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -1548,13 +1548,16 @@ public CSG intersect(CSG csg) { e.printStackTrace(); } } -// triangulate(); -// csg.triangulate(); + if (getPolygons().size() == 0 || csg.getPolygons().size() == 0) { Exception ex = new Exception("Error! Intersection is invalid when one CSG has no polygons!"); ex.printStackTrace(); return CSG.fromPolygons(new ArrayList()).historySync(this).historySync(csg); } + if(defaultOptType==OptType.Manifold3d) { + new RuntimeException("Manifold3d not implemented here").printStackTrace(); + } + Node a; try { a = new Node(this.clone().getPolygons(), this.getPolygons().get(0).getPlane()); From 640269757baeeb2b648e8f69755bea2de82b9132 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 12:11:41 -0400 Subject: [PATCH 19/44] formatting --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 83 +++++++++---------- .../vrl/v3d/ext/imagej/STLLoader.java | 2 +- .../vrl/v3d/ext/quickhull3d/HullUtil.java | 2 +- .../eu/mihosoft/vrl/v3d/Manifold3d_test.java | 4 - 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index aa72cdf0..15a2b54d 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -42,7 +42,6 @@ import eu.mihosoft.vrl.v3d.parametrics.Parameter; import java.io.File; -import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Field; import java.time.Duration; @@ -195,7 +194,7 @@ public void progressUpdate(int currentIndex, int finalIndex, String type, CSG in private int pointsAdded; private String uniqueId = UUID.randomUUID().toString(); - private static ManifoldBindings manifold=null; + private static ManifoldBindings manifold = null; /** * Instantiates a new csg. @@ -890,15 +889,15 @@ public CSG union(CSG csg) { // triangulate(); // csg.triangulate(); switch (getOptType()) { - case CSG_BOUND: - return _unionCSGBoundsOpt(csg).historySync(this).historySync(csg); - case POLYGON_BOUND: - return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); - case Manifold3d: - new RuntimeException("Not implemented yet").printStackTrace(); - default: - // return _unionIntersectOpt(csg); - return _unionNoOpt(csg).historySync(this).historySync(csg); + case CSG_BOUND : + return _unionCSGBoundsOpt(csg).historySync(this).historySync(csg); + case POLYGON_BOUND : + return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); + case Manifold3d : + new RuntimeException("Not implemented yet").printStackTrace(); + default : + // return _unionIntersectOpt(csg); + return _unionNoOpt(csg).historySync(this).historySync(csg); } } @@ -1416,14 +1415,14 @@ public CSG difference(CSG csg) { // polygons if (this.getPolygons().size() > 0 && csg.getPolygons().size() > 0) { switch (getOptType()) { - case CSG_BOUND: - return _differenceCSGBoundsOpt(csg).historySync(this).historySync(csg); - case POLYGON_BOUND: - return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); - case Manifold3d: - new RuntimeException("Not implemented yet").printStackTrace(); - default: - return _differenceNoOpt(csg).historySync(this).historySync(csg); + case CSG_BOUND : + return _differenceCSGBoundsOpt(csg).historySync(this).historySync(csg); + case POLYGON_BOUND : + return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); + case Manifold3d : + new RuntimeException("Not implemented yet").printStackTrace(); + default : + return _differenceNoOpt(csg).historySync(this).historySync(csg); } } else @@ -1438,16 +1437,16 @@ public CSG difference(CSG csg) { if (intersectingParts.getPolygons().size() > 0) { switch (getOptType()) { - case CSG_BOUND: - return _differenceCSGBoundsOpt(intersectingParts).historySync(this) - .historySync(intersectingParts); - case POLYGON_BOUND: - return _differencePolygonBoundsOpt(intersectingParts).historySync(this) - .historySync(intersectingParts); - case Manifold3d: - new RuntimeException("Not implemented yet").printStackTrace(); - default: - return _differenceNoOpt(intersectingParts).historySync(this).historySync(intersectingParts); + case CSG_BOUND : + return _differenceCSGBoundsOpt(intersectingParts).historySync(this) + .historySync(intersectingParts); + case POLYGON_BOUND : + return _differencePolygonBoundsOpt(intersectingParts).historySync(this) + .historySync(intersectingParts); + case Manifold3d : + new RuntimeException("Not implemented yet").printStackTrace(); + default : + return _differenceNoOpt(intersectingParts).historySync(this).historySync(intersectingParts); } } else return this; @@ -1594,10 +1593,10 @@ public CSG intersect(CSG csg) { ex.printStackTrace(); return CSG.fromPolygons(new ArrayList()).historySync(this).historySync(csg); } - if(defaultOptType==OptType.Manifold3d) { + if (defaultOptType == OptType.Manifold3d) { new RuntimeException("Manifold3d not implemented here").printStackTrace(); } - + Node a; try { a = new Node(this.clone().getPolygons(), this.getPolygons().get(0).getPlane()); @@ -1719,19 +1718,19 @@ public String toStlString() { toStlString(sb); return sb.toString(); } - + public CSG to3mf(File target) { - if(defaultOptType == OptType.Manifold3d) { + if (defaultOptType == OptType.Manifold3d) { new RuntimeException("Manifold3d 3mf export not implemented yet").printStackTrace(); - }else { + } else { throw new RuntimeException("Non-Manifold3d 3mf export not implemented yet"); } return this; } - public static CSG loadFrom3mf(File target) { - if(defaultOptType == OptType.Manifold3d) { + public static CSG loadFrom3mf(File target) { + if (defaultOptType == OptType.Manifold3d) { new RuntimeException("Manifold3d 3mf export not implemented yet").printStackTrace(); - }else { + } else { throw new RuntimeException("Non-Manifold3d 3mf export not implemented yet"); } return null; @@ -1745,7 +1744,7 @@ public static CSG loadFrom3mf(File target) { * @return the specified string builder */ public StringBuilder toStlString(StringBuilder sb) { - if(defaultOptType == OptType.Manifold3d) { + if (defaultOptType == OptType.Manifold3d) { new RuntimeException("Manifold3d STL export not implemented yet").printStackTrace(); } triangulate(false); @@ -2804,11 +2803,11 @@ protected OptType getOptType() { * the optType to set */ public static void setDefaultOptType(OptType optType) { - if(optType == OptType.Manifold3d) { + if (optType == OptType.Manifold3d) { try { manifold = new ManifoldBindings(); Slice.setSliceEngine(new ISlice() { - + @Override public List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) throws ColinearPointsException { @@ -2819,7 +2818,7 @@ public List slice(CSG incoming, Transform slicePlane, double normalInse } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); - optType=defaultOptType; + optType = defaultOptType; } } defaultOptType = optType; @@ -2860,7 +2859,7 @@ public static enum OptType { /** The polygon bound. */ POLYGON_BOUND, - + Manifold3d, /** The none. */ diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java index 02bad020..217d6cb9 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/imagej/STLLoader.java @@ -49,7 +49,7 @@ public STLLoader() { * Signals that an I/O exception has occurred. */ public ArrayList parse(File f) throws IOException { - if(CSG.getDefaultOptionType()==OptType.Manifold3d) { + if (CSG.getDefaultOptionType() == OptType.Manifold3d) { new RuntimeException("Manifold3d STL import not implemented yet").printStackTrace(); } ArrayList polygons = new ArrayList<>(); diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java index d03f0b7b..364a0f49 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java @@ -72,7 +72,7 @@ public static CSG hull(List points, PropertyStorage storage) { e.printStackTrace(); } } - if(CSG.getDefaultOptionType()==OptType.Manifold3d) { + if (CSG.getDefaultOptionType() == OptType.Manifold3d) { new RuntimeException("Not implemented yet").printStackTrace(); } Point3d[] hullPoints = points.stream().map((vec) -> new Point3d(vec.x, vec.y, vec.z)).toArray(Point3d[]::new); diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index 96127e21..6f1f4d09 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -1,11 +1,7 @@ package eu.mihosoft.vrl.v3d; -import java.lang.foreign.MemorySegment; - import org.junit.Test; -import com.cadoodlecad.manifold.ManifoldBindings; - import eu.mihosoft.vrl.v3d.CSG.OptType; public class Manifold3d_test { From a5d08b85f091ff0ec11f46d9eed306d05add434f Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 12:14:33 -0400 Subject: [PATCH 20/44] adding the spotless flags --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 473eeed0..945eb6b7 100644 --- a/build.gradle +++ b/build.gradle @@ -8,8 +8,9 @@ plugins { } spotless { java { + lineEndings = com.diffplug.spotless.LineEnding.UNIX // Eclipse formatter with your config file - eclipse() // Uses Eclipse's built-in default profile — no XML needed! + eclipse('4.26') // Uses Eclipse's built-in default profile — no XML needed! // Optional but recommended additions: removeUnusedImports() trimTrailingWhitespace() From bc439873459bc573af26c25b01e6d3b46a8b499a Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Mon, 16 Mar 2026 12:22:50 -0400 Subject: [PATCH 21/44] enforce the file line endings --- .gitattributes | 2 + box.svg | 168 +++++++++++++++++++++---------------------- gradlew.bat | 188 ++++++++++++++++++++++++------------------------- 3 files changed, 180 insertions(+), 178 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1489a259 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=lf +*.java eol=lf \ No newline at end of file diff --git a/box.svg b/box.svg index cbd7f4bd..58c19fa8 100644 --- a/box.svg +++ b/box.svg @@ -1,85 +1,85 @@ - - - -ABox - - -Box - ABox -2024-10-27 21:50:41 -https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en&render=1 -https://boxes.hackerspace-bamberg.de/ABox -A simple Box - -This box is kept simple on purpose. If you need more features have a look at the UniversalBox. - -Created with Boxes.py (https://boxes.hackerspace-bamberg.de/) -Command line: boxes ABox --FingerJoint_style=rectangular --FingerJoint_surroundingspaces=2.0 --FingerJoint_bottom_lip=0.0 --FingerJoint_edge_width=1.0 --FingerJoint_extra_length=0.0 --FingerJoint_finger=2.0 --FingerJoint_play=0.0 --FingerJoint_space=2.0 --FingerJoint_width=1.0 --Lid_handle=none --Lid_style=none --Lid_handle_height=8.0 --Lid_height=4.0 --Lid_play=0.1 --x=100.0 --y=100.0 --h=100.0 --outside=0 --outside=1 --bottom_edge=h --thickness=3.0 --format=svg --tabs=0.0 --qr_code=0 --debug=0 --labels=0 --labels=1 --reference=100.0 --inner_corners=loop --burn=0.1 -Command line short: boxes ABox -Url: https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en&render=1 -Url short: https://boxes.hackerspace-bamberg.de/ABox -SettingsUrl: https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en -SettingsUrl short: https://boxes.hackerspace-bamberg.de/ABox - - - - - 100.0mm, burn:0.10mm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + +ABox + + +Box - ABox +2024-10-27 21:50:41 +https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en&render=1 +https://boxes.hackerspace-bamberg.de/ABox +A simple Box + +This box is kept simple on purpose. If you need more features have a look at the UniversalBox. + +Created with Boxes.py (https://boxes.hackerspace-bamberg.de/) +Command line: boxes ABox --FingerJoint_style=rectangular --FingerJoint_surroundingspaces=2.0 --FingerJoint_bottom_lip=0.0 --FingerJoint_edge_width=1.0 --FingerJoint_extra_length=0.0 --FingerJoint_finger=2.0 --FingerJoint_play=0.0 --FingerJoint_space=2.0 --FingerJoint_width=1.0 --Lid_handle=none --Lid_style=none --Lid_handle_height=8.0 --Lid_height=4.0 --Lid_play=0.1 --x=100.0 --y=100.0 --h=100.0 --outside=0 --outside=1 --bottom_edge=h --thickness=3.0 --format=svg --tabs=0.0 --qr_code=0 --debug=0 --labels=0 --labels=1 --reference=100.0 --inner_corners=loop --burn=0.1 +Command line short: boxes ABox +Url: https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en&render=1 +Url short: https://boxes.hackerspace-bamberg.de/ABox +SettingsUrl: https://boxes.hackerspace-bamberg.de/ABox?FingerJoint_style=rectangular&FingerJoint_surroundingspaces=2.0&FingerJoint_bottom_lip=0.0&FingerJoint_edge_width=1.0&FingerJoint_extra_length=0.0&FingerJoint_finger=2.0&FingerJoint_play=0.0&FingerJoint_space=2.0&FingerJoint_width=1.0&Lid_handle=none&Lid_style=none&Lid_handle_height=8.0&Lid_height=4.0&Lid_play=0.1&x=100.0&y=100.0&h=100.0&outside=0&outside=1&bottom_edge=h&thickness=3.0&format=svg&tabs=0.0&qr_code=0&debug=0&labels=0&labels=1&reference=100.0&inner_corners=loop&burn=0.1&language=en +SettingsUrl short: https://boxes.hackerspace-bamberg.de/ABox + + + + + 100.0mm, burn:0.10mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..9d21a218 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,94 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From afd81b07961aab29c09fec0da6afc78f6ff2d6e1 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Wed, 18 Mar 2026 11:15:58 -0400 Subject: [PATCH 22/44] update version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 945eb6b7..4e207b1f 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:3.2.16") + implementation("com.github.madhephaestus:manifold3d:3.4.0") } From 5d5be5bd271c43c842ae626c4df6dc12dd892f46 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Wed, 25 Mar 2026 18:10:06 -0400 Subject: [PATCH 23/44] Updating to latest release of Manifold --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 37be5314..9abadaa1 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:v3.4.1") + implementation("com.github.madhephaestus:manifold3d:v3.4.1-1") } From 2d16bc591c3191062acdb2b50ac7bb6fef0428fd Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Tue, 7 Apr 2026 09:57:42 -0400 Subject: [PATCH 24/44] Adding the importer and exported methods to wrap manifold in JCSG classes --- .../manifold3d/CSGManifold3d.java | 16 +++ .../manifold3d/Manifold3dExporter.java | 131 ++++++++++++++++++ .../manifold3d/Manifold3dImporter.java | 99 +++++++++++++ src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 5 +- 4 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java create mode 100644 src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java create mode 100644 src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java diff --git a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java new file mode 100644 index 00000000..0c45ef07 --- /dev/null +++ b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java @@ -0,0 +1,16 @@ +package com.neuronrobotics.manifold3d; + +import com.cadoodlecad.manifold.ManifoldBindings; + +public class CSGManifold3d { + private final ManifoldBindings manifold; + Manifold3dExporter exporter; + Manifold3dImporter importer; + public CSGManifold3d() throws Exception { + this.manifold = new ManifoldBindings(); + exporter = new Manifold3dExporter(manifold); + importer = new Manifold3dImporter(manifold); + } + + +} diff --git a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java new file mode 100644 index 00000000..49395612 --- /dev/null +++ b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java @@ -0,0 +1,131 @@ +package com.neuronrobotics.manifold3d; + +import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.cadoodlecad.manifold.ManifoldBindings; + +import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.Polygon; +import eu.mihosoft.vrl.v3d.Vertex; + +/** + * Exports a JCSG {@link CSG} object into a native manifold3d manifold via the bridge API. + * + *

Usage: + *

{@code
+ * Manifold3dBridge bridge = ...; // your wrapper holding the MethodHandle map
+ * CSG csg = ...;
+ *
+ * MemorySegment manifoldSeg = new Manifold3dExporter(bridge).toManifold(csg);
+ * // use manifoldSeg with bridge boolean operations, then clean up via bridge.delete(...)
+ * }
+ * + *

JCSG polygons may have more than three vertices (the BSP representation preserves + * quads and n-gons). This exporter triangulates each polygon using a simple fan from the + * first vertex (safe for convex polygons, which is guaranteed by the JCSG BSP). The + * resulting triangle soup is de-duplicated into an indexed mesh before being handed to + * the bridge so that manifold3d's merge step has shared vertices to work with. + */ +public class Manifold3dExporter { + + private final ManifoldBindings bridge; + + public Manifold3dExporter(ManifoldBindings bridge) { + if (bridge == null) + throw new IllegalArgumentException("bridge must not be null"); + this.bridge = bridge; + } + + /** + * Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}. + * + *

The caller is responsible for eventually freeing the returned segment via the + * bridge's delete method (e.g. {@code manifold_delete_manifold}). + * + * @param csg the solid to export; must not be null + * @return a native manifold segment ready for boolean operations + * @throws Throwable if the native import call fails + * @throws IllegalArgumentException if {@code csg} is null or has no polygons + */ + public MemorySegment toManifold(CSG csg) throws Throwable { + if (csg == null) + throw new IllegalArgumentException("csg must not be null"); + + List polygons = csg.getPolygons(); + if (polygons == null || polygons.isEmpty()) + throw new IllegalArgumentException("CSG has no polygons"); + + // Build an indexed triangle mesh. + // Use a tolerance-free exact key so we don't merge numerically-close-but-distinct verts. + Map vertexIndex = new HashMap<>(); + List vertexList = new ArrayList<>(); + List triList = new ArrayList<>(); + + for (Polygon poly : polygons) { + List pverts = poly.getVertices(); + if (pverts == null || pverts.size() < 3) + continue; + + // Fan triangulation: (0,1,2), (0,2,3), (0,3,4), ... + int i0 = intern(pverts.get(0), vertexIndex, vertexList); + for (int i = 1; i < pverts.size() - 1; i++) { + int i1 = intern(pverts.get(i), vertexIndex, vertexList); + int i2 = intern(pverts.get(i + 1), vertexIndex, vertexList); + + // Skip degenerate triangles (two or more identical indices). + if (i0 == i1 || i1 == i2 || i0 == i2) + continue; + + triList.add((long) i0); + triList.add((long) i1); + triList.add((long) i2); + } + } + + if (triList.isEmpty()) + throw new IllegalArgumentException("CSG produced no valid triangles after triangulation"); + + long nVerts = vertexList.size(); + long nTris = triList.size() / 3; + + // Flatten vertex list into a primitive array. + double[] vertices = new double[(int) (nVerts * 3)]; + for (int i = 0; i < nVerts; i++) { + double[] v = vertexList.get(i); + vertices[i * 3] = v[0]; + vertices[i * 3 + 1] = v[1]; + vertices[i * 3 + 2] = v[2]; + } + + // Flatten triangle index list. + long[] triangles = new long[triList.size()]; + for (int i = 0; i < triList.size(); i++) { + triangles[i] = triList.get(i); + } + + return bridge.importMeshGL64(vertices, triangles, nVerts, nTris); + } + + // ------------------------------------------------------------------------- + // helpers + + /** + * Returns the index of {@code v} in {@code vertexList}, inserting it if not already present. + * The key is an exact string representation of (x, y, z) using {@link Double#toHexString} + * so that only bit-identical positions are merged, matching the BSP's behaviour. + */ + private static int intern(Vertex v, Map index, List list) { + String key = Double.toHexString(v.pos.x) + "," + Double.toHexString(v.pos.y) + "," + + Double.toHexString(v.pos.z); + + return index.computeIfAbsent(key, k -> { + int idx = list.size(); + list.add(new double[] { v.pos.x, v.pos.y, v.pos.z }); + return idx; + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java new file mode 100644 index 00000000..d4b3cb9a --- /dev/null +++ b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java @@ -0,0 +1,99 @@ +package com.neuronrobotics.manifold3d; + +import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.Polygon; +import eu.mihosoft.vrl.v3d.Vector3d; +import eu.mihosoft.vrl.v3d.Vertex; +import eu.mihosoft.vrl.v3d.ext.org.poly2tri.PolygonUtil; + +import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.cadoodlecad.manifold.ManifoldBindings; +import com.cadoodlecad.manifold.ManifoldBindings.MeshData64; + +/** + * Imports a manifold3d mesh (via its native bridge API) into a JCSG {@link CSG} object. + * + *

Usage: + *

{@code
+ * Manifold3dBridge bridge = ...; // your wrapper holding the MethodHandle map
+ * MemorySegment manifoldSeg = bridge.importMeshGL64(vertices, triangles, nVerts, nTris);
+ *
+ * CSG csg = new Manifold3dImporter(bridge).fromManifold(manifoldSeg);
+ * }
+ * + *

The bridge instance must expose {@code exportMeshGL64(MemorySegment)} returning a + * {@code MeshData64} record with fields {@code double[] vertices}, {@code long[] triangles}, + * {@code int vertCount}, and {@code int triCount}. + */ +public class Manifold3dImporter { + + + + private ManifoldBindings bridge; + + public Manifold3dImporter(ManifoldBindings bridge) { + if (bridge == null) + throw new IllegalArgumentException("bridge must not be null"); + this.bridge = bridge; + } + + /** + * Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}. + * + *

The manifold is exported as a triangle soup via the bridge's {@code exportMeshGL64} + * method. Each triangle becomes one JCSG {@link Polygon} (three {@link Vertex} objects + * with positions taken from the flat vertex array). Per-vertex normals are computed + * as the face normal so that JCSG downstream tools (BSP, boolean ops) have valid planes. + * + * @param manifold native manifold segment returned by the bridge import call + * @return a new {@link CSG} representing the same geometry + * @throws Throwable if the native export call fails + * @throws IllegalArgumentException if {@code manifold} is null + */ + public CSG fromManifold(MemorySegment manifold) throws Throwable { + if (manifold == null) + throw new IllegalArgumentException("manifold segment must not be null"); + + MeshData64 mesh = bridge.exportMeshGL64(manifold); + + double[] verts = mesh.vertices(); // flat [x0,y0,z0, x1,y1,z1, ...] + long[] tris = mesh.triangles(); // flat [i0,i1,i2, i3,i4,i5, ...] + int triCount = mesh.triCount(); + + if (triCount == 0) + return new CSG(); + + ArrayList polygons = new ArrayList<>(triCount); + + for (int t = 0; t < triCount; t++) { + int base = t * 3; + + Vector3d p0 = vertexAt(verts, (int) tris[base]); + Vector3d p1 = vertexAt(verts, (int) tris[base + 1]); + Vector3d p2 = vertexAt(verts, (int) tris[base + 2]); + + // Face normal (used as the vertex normal for all three corners). + Vector3d edge1 = p1.minus(p0); + Vector3d edge2 = p2.minus(p0); + Vector3d normal = edge1.cross(edge2).normalized(); + + List vertices = Arrays.asList(new Vertex(p0), new Vertex(p1), new Vertex(p2)); + + polygons.add(new Polygon(vertices)); + } + + return CSG.fromPolygons(polygons); + } + + // ------------------------------------------------------------------------- + // helpers + + private static Vector3d vertexAt(double[] verts, int index) { + int base = index * 3; + return new Vector3d(verts[base], verts[base + 1], verts[base + 2]); + } +} \ No newline at end of file diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 15a2b54d..80f69598 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -65,6 +65,7 @@ import com.aparapi.internal.kernel.KernelRunner; import com.cadoodlecad.manifold.ManifoldBindings; import com.neuronrobotics.interaction.CadInteractionEvent; +import com.neuronrobotics.manifold3d.CSGManifold3d; import javafx.scene.paint.Color; import javafx.scene.paint.PhongMaterial; @@ -194,7 +195,7 @@ public void progressUpdate(int currentIndex, int finalIndex, String type, CSG in private int pointsAdded; private String uniqueId = UUID.randomUUID().toString(); - private static ManifoldBindings manifold = null; + private static CSGManifold3d manifold = null; /** * Instantiates a new csg. @@ -2805,7 +2806,7 @@ protected OptType getOptType() { public static void setDefaultOptType(OptType optType) { if (optType == OptType.Manifold3d) { try { - manifold = new ManifoldBindings(); + manifold = new CSGManifold3d(); Slice.setSliceEngine(new ISlice() { @Override From 278fca5f20edb36027739b650ab7463f9acd09d2 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Tue, 7 Apr 2026 14:04:24 -0400 Subject: [PATCH 25/44] adding a basic set of operations --- build.gradle | 2 +- .../manifold3d/CSGManifold3d.java | 273 +++++++++++++++++- 2 files changed, 270 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 9abadaa1..314c0d13 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:v3.4.1-1") + implementation("com.github.madhephaestus:manifold3d:v3.4.1-2") } diff --git a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java index 0c45ef07..6f667359 100644 --- a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java +++ b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java @@ -1,16 +1,281 @@ package com.neuronrobotics.manifold3d; +import java.io.File; +import java.lang.foreign.MemorySegment; + import com.cadoodlecad.manifold.ManifoldBindings; +import eu.mihosoft.vrl.v3d.CSG; +import eu.mihosoft.vrl.v3d.Polygon; +import eu.mihosoft.vrl.v3d.Vertex; + public class CSGManifold3d { private final ManifoldBindings manifold; - Manifold3dExporter exporter; - Manifold3dImporter importer; + private final Manifold3dExporter exporter; + private final Manifold3dImporter importer; + public CSGManifold3d() throws Exception { this.manifold = new ManifoldBindings(); exporter = new Manifold3dExporter(manifold); importer = new Manifold3dImporter(manifold); } - - + + /** + * Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}. + * + *

The caller is responsible for eventually freeing the returned segment via the + * bridge's delete method (e.g. {@code manifold_delete_manifold}). + * + * @param csg the solid to export; must not be null + * @return a native manifold segment ready for boolean operations + * @throws Throwable if the native import call fails + * @throws IllegalArgumentException if {@code csg} is null or has no polygons + */ + private MemorySegment toManifold(CSG csg) throws Throwable { + return exporter.toManifold(csg); + } + + /** + * Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}. + * + *

The manifold is exported as a triangle soup via the bridge's {@code exportMeshGL64} + * method. Each triangle becomes one JCSG {@link Polygon} (three {@link Vertex} objects + * with positions taken from the flat vertex array). Per-vertex normals are computed + * as the face normal so that JCSG downstream tools (BSP, boolean ops) have valid planes. + * + * @param manifold native manifold segment returned by the bridge import call + * @return a new {@link CSG} representing the same geometry + * @throws Throwable if the native export call fails + * @throws IllegalArgumentException if {@code manifold} is null + */ + private CSG fromManifold(MemorySegment manifold) throws Throwable { + return importer.fromManifold(manifold); + } + + // ------------------------------------------------------------------------- + // Boolean operations + // ------------------------------------------------------------------------- + + /** + * Returns the union of two CSG solids. + * Uses {@code manifold.union(a, b)} directly (wrapper around + * {@code manifold_union} in the C library). + */ + public CSG union(CSG a, CSG b) throws Throwable { + MemorySegment ma = toManifold(a); + MemorySegment mb = toManifold(b); + try { + MemorySegment result = manifold.union(ma, mb); + return fromManifold(result); + } finally { + manifold.deleteMeshGL64(ma); + manifold.deleteMeshGL64(mb); + } + } + + /** + * Returns the difference of two CSG solids (a minus b). + * Uses {@code manifold.difference(a, b)}. + */ + public CSG difference(CSG a, CSG b) throws Throwable { + MemorySegment ma = toManifold(a); + MemorySegment mb = toManifold(b); + try { + MemorySegment result = manifold.difference(ma, mb); + return fromManifold(result); + } finally { + manifold.deleteMeshGL64(ma); + manifold.deleteMeshGL64(mb); + } + } + + /** + * Returns the intersection of two CSG solids. + * Uses {@code manifold.intersection(a, b)}. + */ + public CSG intersection(CSG a, CSG b) throws Throwable { + MemorySegment ma = toManifold(a); + MemorySegment mb = toManifold(b); + try { + MemorySegment result = manifold.intersection(ma, mb); + return fromManifold(result); + } finally { + manifold.deleteMeshGL64(ma); + manifold.deleteMeshGL64(mb); + } + } + + // ------------------------------------------------------------------------- + // Convex hull + // ------------------------------------------------------------------------- + + /** + * Returns the convex hull of two CSG solids combined. + *

+ * Manifold's {@code manifold_hull} operates on a single manifold, so we + * first union the two inputs to combine their point sets, then hull the + * result via {@code manifold.hull(MemorySegment)}. + */ + public CSG hull(CSG a) throws Throwable { + MemorySegment ma = toManifold(a); + try { + MemorySegment result = manifold.hull(ma); + return fromManifold(result); + } finally { + manifold.deleteMeshGL64(ma); + } + } + + /** + * Convenience overload: convex hull over an arbitrary number of CSG solids. + * Uses {@code manifold.batchHull(MemorySegment[])} which maps to + * {@code manifold_batch_hull}. + */ + public CSG hull(CSG... solids) throws Throwable { + MemorySegment[] segs = new MemorySegment[solids.length]; + for (int i = 0; i < solids.length; i++) + segs[i] = toManifold(solids[i]); + try { + MemorySegment result = manifold.batchHull(segs); + return fromManifold(result); + } finally { + for (MemorySegment seg : segs) + manifold.deleteMeshGL64(seg); + } + } + + // ------------------------------------------------------------------------- + // Slice at a plane → List + // ------------------------------------------------------------------------- + +// /** +// * Slices a CSG solid at a horizontal plane (Z = height) and returns the +// * resulting cross-section contours as JCSG {@link Polygon}s. +// *

+// * Manifold's {@code manifold_slice(mem, m, height)} always cuts perpendicular +// * to the Z axis and returns a {@code ManifoldPolygons*}. Each contour ring +// * is reconstructed here as a JCSG {@link Polygon} lying in the XY plane at +// * {@code z = height}. +// *

+// * To cut along an arbitrary plane, rotate the solid so that the desired +// * normal aligns with +Z, call this method, then reverse-rotate the polygons. +// * +// * @param csg the solid to slice +// * @param height Z coordinate of the cutting plane +// * @return cross-section polygons (may be empty for solids that don't reach +// * that height) +// * @throws Throwable if the native call fails +// */ +// public List sliceAtZ(CSG csg, double height) throws Throwable { +// MemorySegment m = toManifold(csg); +// try { +// // manifold.slice(MemorySegment m, double height) → +// // ManifoldPolygons* manifold_slice(void* mem, ManifoldManifold* m, double height) +// MemorySegment polygonsSeg = manifold.slice(m, height); +// try { +// return importer.fromManifoldPolygons(polygonsSeg, height); +// } finally { +// manifold.deletePolygons(polygonsSeg); +// } +// } finally { +// manifold.deleteMeshGL64(m); +// } +// } + + // ------------------------------------------------------------------------- + // STL import / export + // ------------------------------------------------------------------------- + +// /** +// * Exports a CSG solid to an STL file via Manifold's mesh pipeline. +// *

+// * The geometry is round-tripped through Manifold's MeshGL64 representation +// * and written by the {@link Manifold3dSTLExporter} helper. +// *

+// * Note: STL is lossy – it has no topology encoding. Prefer 3MF for +// * any round-trip use-case. +// * +// * @param csg the solid to export +// * @param file destination STL file (created or overwritten) +// * @throws Throwable if export or file-write fails +// */ +// public void exportSTL(CSG csg, File file) throws Throwable { +// MemorySegment m = toManifold(csg); +// try { +// ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m); +// Manifold3dSTLExporter.write(mesh, file); +// } finally { +// manifold.deleteMeshGL64(m); +// } +// } +// +// /** +// * Imports an STL file and returns its geometry as a CSG solid. +// *

+// * The file is parsed by {@link Manifold3dSTLImporter} into raw vertex/triangle +// * arrays that are fed into {@code manifold.importMeshGL64(...)}, which merges +// * duplicate vertices and validates manifoldness before handing back a native +// * manifold segment that is then converted to CSG. +// * +// * @param file the STL file to read +// * @return a new {@link CSG} representing the imported geometry +// * @throws Throwable if parsing or the native import fails +// * @throws IllegalArgumentException if the file does not exist +// */ +// public CSG importSTL(File file) throws Throwable { +// if (!file.exists()) +// throw new IllegalArgumentException("STL file not found: " + file.getAbsolutePath()); +// Manifold3dSTLImporter.RawMesh raw = Manifold3dSTLImporter.read(file); +// MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), raw.vertCount(), raw.triCount()); +// try { +// return fromManifold(m); +// } finally { +// manifold.deleteMeshGL64(m); +// } +// } +// +// // ------------------------------------------------------------------------- +// // 3MF import / export +// // ------------------------------------------------------------------------- +// +// /** +// * Exports a CSG solid to a 3MF file. +// *

+// * 3MF preserves topology and is lossless – recommended over STL for any +// * workflow that re-imports the file. +// * +// * @param csg the solid to export +// * @param file destination 3MF file (created or overwritten) +// * @throws Throwable if export or file-write fails +// */ +// public void export3MF(CSG csg, File file) throws Throwable { +// MemorySegment m = toManifold(csg); +// try { +// ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m); +// Manifold3d3MFExporter.write(mesh, file); +// } finally { +// manifold.deleteMeshGL64(m); +// } +// } +// +// /** +// * Imports a 3MF file and returns the first body as a CSG solid. +// * +// * @param file the 3MF file to read +// * @return a new {@link CSG} representing the imported geometry +// * @throws Throwable if parsing or the native import fails +// * @throws IllegalArgumentException if the file does not exist +// */ +// public CSG import3MF(File file) throws Throwable { +// if (!file.exists()) +// throw new IllegalArgumentException("3MF file not found: " + file.getAbsolutePath()); +// Manifold3dSTLImporter.RawMesh raw = Manifold3d3MFImporter.read(file); +// MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), raw.vertCount(), raw.triCount()); +// try { +// return fromManifold(m); +// } finally { +// manifold.deleteMeshGL64(m); +// } +// } + } From 64b5e947cbe72b1373651e78d168c323a59c4302 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Tue, 7 Apr 2026 14:18:51 -0400 Subject: [PATCH 26/44] make the default engine setable --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 3 +++ src/main/java/eu/mihosoft/vrl/v3d/Slice.java | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 80f69598..828234f3 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -2821,6 +2821,9 @@ public List slice(CSG incoming, Transform slicePlane, double normalInse e.printStackTrace(); optType = defaultOptType; } + }else { + Slice.setSliceEngine(null ); + Slice.getSliceEngine();// set the default when the engine is null } defaultOptType = optType; } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Slice.java b/src/main/java/eu/mihosoft/vrl/v3d/Slice.java index 0452923e..79015c86 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Slice.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Slice.java @@ -419,7 +419,7 @@ boolean withinAPix(int[] incoming, int[] out) { } }; - private static ISlice sliceEngine = new DefaultSliceImp(); + private static ISlice sliceEngine ; /** * Returns true if this polygon lies entirely in the z plane @@ -498,6 +498,8 @@ public static List slice(CSG incoming, double normalInsetDistance) thro return slice(incoming, new Transform(), normalInsetDistance); } public static ISlice getSliceEngine() { + if(sliceEngine==null) + sliceEngine= new DefaultSliceImp(); return sliceEngine; } From bb65f3c0a72fbbec1601cb7bcd791ad300078a6f Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Tue, 7 Apr 2026 17:43:39 -0400 Subject: [PATCH 27/44] formatting --- .../manifold3d/CSGManifold3d.java | 361 +++++++++++------- .../manifold3d/Manifold3dExporter.java | 45 ++- .../manifold3d/Manifold3dImporter.java | 40 +- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 5 +- src/main/java/eu/mihosoft/vrl/v3d/Slice.java | 6 +- 5 files changed, 270 insertions(+), 187 deletions(-) diff --git a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java index 6f667359..21523445 100644 --- a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java +++ b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java @@ -1,12 +1,14 @@ package com.neuronrobotics.manifold3d; -import java.io.File; import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.List; import com.cadoodlecad.manifold.ManifoldBindings; import eu.mihosoft.vrl.v3d.CSG; import eu.mihosoft.vrl.v3d.Polygon; +import eu.mihosoft.vrl.v3d.Vector3d; import eu.mihosoft.vrl.v3d.Vertex; public class CSGManifold3d { @@ -23,13 +25,17 @@ public CSGManifold3d() throws Exception { /** * Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}. * - *

The caller is responsible for eventually freeing the returned segment via the + *

+ * The caller is responsible for eventually freeing the returned segment via the * bridge's delete method (e.g. {@code manifold_delete_manifold}). * - * @param csg the solid to export; must not be null + * @param csg + * the solid to export; must not be null * @return a native manifold segment ready for boolean operations - * @throws Throwable if the native import call fails - * @throws IllegalArgumentException if {@code csg} is null or has no polygons + * @throws Throwable + * if the native import call fails + * @throws IllegalArgumentException + * if {@code csg} is null or has no polygons */ private MemorySegment toManifold(CSG csg) throws Throwable { return exporter.toManifold(csg); @@ -38,28 +44,83 @@ private MemorySegment toManifold(CSG csg) throws Throwable { /** * Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}. * - *

The manifold is exported as a triangle soup via the bridge's {@code exportMeshGL64} - * method. Each triangle becomes one JCSG {@link Polygon} (three {@link Vertex} objects - * with positions taken from the flat vertex array). Per-vertex normals are computed - * as the face normal so that JCSG downstream tools (BSP, boolean ops) have valid planes. + *

+ * The manifold is exported as a triangle soup via the bridge's + * {@code exportMeshGL64} method. Each triangle becomes one JCSG {@link Polygon} + * (three {@link Vertex} objects with positions taken from the flat vertex + * array). Per-vertex normals are computed as the face normal so that JCSG + * downstream tools (BSP, boolean ops) have valid planes. * - * @param manifold native manifold segment returned by the bridge import call + * @param manifold + * native manifold segment returned by the bridge import call * @return a new {@link CSG} representing the same geometry - * @throws Throwable if the native export call fails - * @throws IllegalArgumentException if {@code manifold} is null + * @throws Throwable + * if the native export call fails + * @throws IllegalArgumentException + * if {@code manifold} is null */ private CSG fromManifold(MemorySegment manifold) throws Throwable { return importer.fromManifold(manifold); } + /** + * Slices the given CSG at Z=0 and returns the resulting cross-section as a list + * of JCSG {@link Polygon} objects. + * + *

+ * Each polygon is a flat contour in the Z=0 plane. Outer contours are wound + * counter-clockwise; holes are wound clockwise — matching the winding order + * that manifold_slice produces. + * + *

+ * Contours with fewer than 3 vertices are skipped because they cannot form a + * valid polygon. + * + * @param csg + * the solid to slice + * @return closed polygon contours of the cross-section at Z=0, never + * {@code null}, may be empty if the plane misses the solid + * @throws RuntimeException + * wrapping any native call failure + */ + public ArrayList sliceAtZero(CSG csg) { + try { + MemorySegment csgm = toManifold(csg); + List contours = manifold.slice(csgm, 0.0); + + ArrayList result = new ArrayList<>(contours.size()); + + for (double[][] contour : contours) { + // Need at least 3 vertices to define a plane and a valid polygon. + if (contour.length < 3) { + continue; + } + + ArrayList points = new ArrayList<>(contour.length); + for (double[] xy : contour) { + // Z=0 because this is a cross-section at height 0. + points.add(Vector3d.xyz(xy[0], xy[1], 0.0)); + } + + result.add(Polygon.fromPoints(points)); + } + + return result; + + } catch (RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException("Failed to slice CSG at Z=0", e); + } + } + // ------------------------------------------------------------------------- // Boolean operations // ------------------------------------------------------------------------- /** - * Returns the union of two CSG solids. - * Uses {@code manifold.union(a, b)} directly (wrapper around - * {@code manifold_union} in the C library). + * Returns the union of two CSG solids. Uses {@code manifold.union(a, b)} + * directly (wrapper around {@code manifold_union} in the C library). */ public CSG union(CSG a, CSG b) throws Throwable { MemorySegment ma = toManifold(a); @@ -74,8 +135,8 @@ public CSG union(CSG a, CSG b) throws Throwable { } /** - * Returns the difference of two CSG solids (a minus b). - * Uses {@code manifold.difference(a, b)}. + * Returns the difference of two CSG solids (a minus b). Uses + * {@code manifold.difference(a, b)}. */ public CSG difference(CSG a, CSG b) throws Throwable { MemorySegment ma = toManifold(a); @@ -90,8 +151,8 @@ public CSG difference(CSG a, CSG b) throws Throwable { } /** - * Returns the intersection of two CSG solids. - * Uses {@code manifold.intersection(a, b)}. + * Returns the intersection of two CSG solids. Uses + * {@code manifold.intersection(a, b)}. */ public CSG intersection(CSG a, CSG b) throws Throwable { MemorySegment ma = toManifold(a); @@ -112,9 +173,9 @@ public CSG intersection(CSG a, CSG b) throws Throwable { /** * Returns the convex hull of two CSG solids combined. *

- * Manifold's {@code manifold_hull} operates on a single manifold, so we - * first union the two inputs to combine their point sets, then hull the - * result via {@code manifold.hull(MemorySegment)}. + * Manifold's {@code manifold_hull} operates on a single manifold, so we first + * union the two inputs to combine their point sets, then hull the result via + * {@code manifold.hull(MemorySegment)}. */ public CSG hull(CSG a) throws Throwable { MemorySegment ma = toManifold(a); @@ -145,137 +206,143 @@ public CSG hull(CSG... solids) throws Throwable { } // ------------------------------------------------------------------------- - // Slice at a plane → List + // Slice at a plane → List // ------------------------------------------------------------------------- -// /** -// * Slices a CSG solid at a horizontal plane (Z = height) and returns the -// * resulting cross-section contours as JCSG {@link Polygon}s. -// *

-// * Manifold's {@code manifold_slice(mem, m, height)} always cuts perpendicular -// * to the Z axis and returns a {@code ManifoldPolygons*}. Each contour ring -// * is reconstructed here as a JCSG {@link Polygon} lying in the XY plane at -// * {@code z = height}. -// *

-// * To cut along an arbitrary plane, rotate the solid so that the desired -// * normal aligns with +Z, call this method, then reverse-rotate the polygons. -// * -// * @param csg the solid to slice -// * @param height Z coordinate of the cutting plane -// * @return cross-section polygons (may be empty for solids that don't reach -// * that height) -// * @throws Throwable if the native call fails -// */ -// public List sliceAtZ(CSG csg, double height) throws Throwable { -// MemorySegment m = toManifold(csg); -// try { -// // manifold.slice(MemorySegment m, double height) → -// // ManifoldPolygons* manifold_slice(void* mem, ManifoldManifold* m, double height) -// MemorySegment polygonsSeg = manifold.slice(m, height); -// try { -// return importer.fromManifoldPolygons(polygonsSeg, height); -// } finally { -// manifold.deletePolygons(polygonsSeg); -// } -// } finally { -// manifold.deleteMeshGL64(m); -// } -// } + // /** + // * Slices a CSG solid at a horizontal plane (Z = height) and returns the + // * resulting cross-section contours as JCSG {@link Polygon}s. + // *

+ // * Manifold's {@code manifold_slice(mem, m, height)} always cuts perpendicular + // * to the Z axis and returns a {@code ManifoldPolygons*}. Each contour ring + // * is reconstructed here as a JCSG {@link Polygon} lying in the XY plane at + // * {@code z = height}. + // *

+ // * To cut along an arbitrary plane, rotate the solid so that the desired + // * normal aligns with +Z, call this method, then reverse-rotate the polygons. + // * + // * @param csg the solid to slice + // * @param height Z coordinate of the cutting plane + // * @return cross-section polygons (may be empty for solids that don't reach + // * that height) + // * @throws Throwable if the native call fails + // */ + // public List sliceAtZ(CSG csg, double height) throws Throwable { + // MemorySegment m = toManifold(csg); + // try { + // // manifold.slice(MemorySegment m, double height) → + // // ManifoldPolygons* manifold_slice(void* mem, ManifoldManifold* m, double + // height) + // MemorySegment polygonsSeg = manifold.slice(m, height); + // try { + // return importer.fromManifoldPolygons(polygonsSeg, height); + // } finally { + // manifold.deletePolygons(polygonsSeg); + // } + // } finally { + // manifold.deleteMeshGL64(m); + // } + // } // ------------------------------------------------------------------------- // STL import / export // ------------------------------------------------------------------------- -// /** -// * Exports a CSG solid to an STL file via Manifold's mesh pipeline. -// *

-// * The geometry is round-tripped through Manifold's MeshGL64 representation -// * and written by the {@link Manifold3dSTLExporter} helper. -// *

-// * Note: STL is lossy – it has no topology encoding. Prefer 3MF for -// * any round-trip use-case. -// * -// * @param csg the solid to export -// * @param file destination STL file (created or overwritten) -// * @throws Throwable if export or file-write fails -// */ -// public void exportSTL(CSG csg, File file) throws Throwable { -// MemorySegment m = toManifold(csg); -// try { -// ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m); -// Manifold3dSTLExporter.write(mesh, file); -// } finally { -// manifold.deleteMeshGL64(m); -// } -// } -// -// /** -// * Imports an STL file and returns its geometry as a CSG solid. -// *

-// * The file is parsed by {@link Manifold3dSTLImporter} into raw vertex/triangle -// * arrays that are fed into {@code manifold.importMeshGL64(...)}, which merges -// * duplicate vertices and validates manifoldness before handing back a native -// * manifold segment that is then converted to CSG. -// * -// * @param file the STL file to read -// * @return a new {@link CSG} representing the imported geometry -// * @throws Throwable if parsing or the native import fails -// * @throws IllegalArgumentException if the file does not exist -// */ -// public CSG importSTL(File file) throws Throwable { -// if (!file.exists()) -// throw new IllegalArgumentException("STL file not found: " + file.getAbsolutePath()); -// Manifold3dSTLImporter.RawMesh raw = Manifold3dSTLImporter.read(file); -// MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), raw.vertCount(), raw.triCount()); -// try { -// return fromManifold(m); -// } finally { -// manifold.deleteMeshGL64(m); -// } -// } -// -// // ------------------------------------------------------------------------- -// // 3MF import / export -// // ------------------------------------------------------------------------- -// -// /** -// * Exports a CSG solid to a 3MF file. -// *

-// * 3MF preserves topology and is lossless – recommended over STL for any -// * workflow that re-imports the file. -// * -// * @param csg the solid to export -// * @param file destination 3MF file (created or overwritten) -// * @throws Throwable if export or file-write fails -// */ -// public void export3MF(CSG csg, File file) throws Throwable { -// MemorySegment m = toManifold(csg); -// try { -// ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m); -// Manifold3d3MFExporter.write(mesh, file); -// } finally { -// manifold.deleteMeshGL64(m); -// } -// } -// -// /** -// * Imports a 3MF file and returns the first body as a CSG solid. -// * -// * @param file the 3MF file to read -// * @return a new {@link CSG} representing the imported geometry -// * @throws Throwable if parsing or the native import fails -// * @throws IllegalArgumentException if the file does not exist -// */ -// public CSG import3MF(File file) throws Throwable { -// if (!file.exists()) -// throw new IllegalArgumentException("3MF file not found: " + file.getAbsolutePath()); -// Manifold3dSTLImporter.RawMesh raw = Manifold3d3MFImporter.read(file); -// MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), raw.vertCount(), raw.triCount()); -// try { -// return fromManifold(m); -// } finally { -// manifold.deleteMeshGL64(m); -// } -// } + // /** + // * Exports a CSG solid to an STL file via Manifold's mesh pipeline. + // *

+ // * The geometry is round-tripped through Manifold's MeshGL64 representation + // * and written by the {@link Manifold3dSTLExporter} helper. + // *

+ // * Note: STL is lossy – it has no topology encoding. Prefer 3MF for + // * any round-trip use-case. + // * + // * @param csg the solid to export + // * @param file destination STL file (created or overwritten) + // * @throws Throwable if export or file-write fails + // */ + // public void exportSTL(CSG csg, File file) throws Throwable { + // MemorySegment m = toManifold(csg); + // try { + // ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m); + // Manifold3dSTLExporter.write(mesh, file); + // } finally { + // manifold.deleteMeshGL64(m); + // } + // } + // + // /** + // * Imports an STL file and returns its geometry as a CSG solid. + // *

+ // * The file is parsed by {@link Manifold3dSTLImporter} into raw + // vertex/triangle + // * arrays that are fed into {@code manifold.importMeshGL64(...)}, which merges + // * duplicate vertices and validates manifoldness before handing back a native + // * manifold segment that is then converted to CSG. + // * + // * @param file the STL file to read + // * @return a new {@link CSG} representing the imported geometry + // * @throws Throwable if parsing or the native import fails + // * @throws IllegalArgumentException if the file does not exist + // */ + // public CSG importSTL(File file) throws Throwable { + // if (!file.exists()) + // throw new IllegalArgumentException("STL file not found: " + + // file.getAbsolutePath()); + // Manifold3dSTLImporter.RawMesh raw = Manifold3dSTLImporter.read(file); + // MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), + // raw.vertCount(), raw.triCount()); + // try { + // return fromManifold(m); + // } finally { + // manifold.deleteMeshGL64(m); + // } + // } + // + // // ------------------------------------------------------------------------- + // // 3MF import / export + // // ------------------------------------------------------------------------- + // + // /** + // * Exports a CSG solid to a 3MF file. + // *

+ // * 3MF preserves topology and is lossless – recommended over STL for any + // * workflow that re-imports the file. + // * + // * @param csg the solid to export + // * @param file destination 3MF file (created or overwritten) + // * @throws Throwable if export or file-write fails + // */ + // public void export3MF(CSG csg, File file) throws Throwable { + // MemorySegment m = toManifold(csg); + // try { + // ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m); + // Manifold3d3MFExporter.write(mesh, file); + // } finally { + // manifold.deleteMeshGL64(m); + // } + // } + // + // /** + // * Imports a 3MF file and returns the first body as a CSG solid. + // * + // * @param file the 3MF file to read + // * @return a new {@link CSG} representing the imported geometry + // * @throws Throwable if parsing or the native import fails + // * @throws IllegalArgumentException if the file does not exist + // */ + // public CSG import3MF(File file) throws Throwable { + // if (!file.exists()) + // throw new IllegalArgumentException("3MF file not found: " + + // file.getAbsolutePath()); + // Manifold3dSTLImporter.RawMesh raw = Manifold3d3MFImporter.read(file); + // MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), + // raw.vertCount(), raw.triCount()); + // try { + // return fromManifold(m); + // } finally { + // manifold.deleteMeshGL64(m); + // } + // } } diff --git a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java index 49395612..f306147e 100644 --- a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java +++ b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java @@ -13,9 +13,12 @@ import eu.mihosoft.vrl.v3d.Vertex; /** - * Exports a JCSG {@link CSG} object into a native manifold3d manifold via the bridge API. + * Exports a JCSG {@link CSG} object into a native manifold3d manifold via the + * bridge API. + * + *

+ * Usage: * - *

Usage: *

{@code
  * Manifold3dBridge bridge = ...; // your wrapper holding the MethodHandle map
  * CSG csg = ...;
@@ -24,11 +27,13 @@
  * // use manifoldSeg with bridge boolean operations, then clean up via bridge.delete(...)
  * }
* - *

JCSG polygons may have more than three vertices (the BSP representation preserves - * quads and n-gons). This exporter triangulates each polygon using a simple fan from the - * first vertex (safe for convex polygons, which is guaranteed by the JCSG BSP). The - * resulting triangle soup is de-duplicated into an indexed mesh before being handed to - * the bridge so that manifold3d's merge step has shared vertices to work with. + *

+ * JCSG polygons may have more than three vertices (the BSP representation + * preserves quads and n-gons). This exporter triangulates each polygon using a + * simple fan from the first vertex (safe for convex polygons, which is + * guaranteed by the JCSG BSP). The resulting triangle soup is de-duplicated + * into an indexed mesh before being handed to the bridge so that manifold3d's + * merge step has shared vertices to work with. */ public class Manifold3dExporter { @@ -43,13 +48,17 @@ public Manifold3dExporter(ManifoldBindings bridge) { /** * Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}. * - *

The caller is responsible for eventually freeing the returned segment via the + *

+ * The caller is responsible for eventually freeing the returned segment via the * bridge's delete method (e.g. {@code manifold_delete_manifold}). * - * @param csg the solid to export; must not be null + * @param csg + * the solid to export; must not be null * @return a native manifold segment ready for boolean operations - * @throws Throwable if the native import call fails - * @throws IllegalArgumentException if {@code csg} is null or has no polygons + * @throws Throwable + * if the native import call fails + * @throws IllegalArgumentException + * if {@code csg} is null or has no polygons */ public MemorySegment toManifold(CSG csg) throws Throwable { if (csg == null) @@ -60,7 +69,8 @@ public MemorySegment toManifold(CSG csg) throws Throwable { throw new IllegalArgumentException("CSG has no polygons"); // Build an indexed triangle mesh. - // Use a tolerance-free exact key so we don't merge numerically-close-but-distinct verts. + // Use a tolerance-free exact key so we don't merge + // numerically-close-but-distinct verts. Map vertexIndex = new HashMap<>(); List vertexList = new ArrayList<>(); List triList = new ArrayList<>(); @@ -114,9 +124,10 @@ public MemorySegment toManifold(CSG csg) throws Throwable { // helpers /** - * Returns the index of {@code v} in {@code vertexList}, inserting it if not already present. - * The key is an exact string representation of (x, y, z) using {@link Double#toHexString} - * so that only bit-identical positions are merged, matching the BSP's behaviour. + * Returns the index of {@code v} in {@code vertexList}, inserting it if not + * already present. The key is an exact string representation of (x, y, z) using + * {@link Double#toHexString} so that only bit-identical positions are merged, + * matching the BSP's behaviour. */ private static int intern(Vertex v, Map index, List list) { String key = Double.toHexString(v.pos.x) + "," + Double.toHexString(v.pos.y) + "," @@ -124,8 +135,8 @@ private static int intern(Vertex v, Map index, List l return index.computeIfAbsent(key, k -> { int idx = list.size(); - list.add(new double[] { v.pos.x, v.pos.y, v.pos.z }); + list.add(new double[]{v.pos.x, v.pos.y, v.pos.z}); return idx; }); } -} \ No newline at end of file +} diff --git a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java index d4b3cb9a..c5a0b7ab 100644 --- a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java +++ b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java @@ -4,7 +4,6 @@ import eu.mihosoft.vrl.v3d.Polygon; import eu.mihosoft.vrl.v3d.Vector3d; import eu.mihosoft.vrl.v3d.Vertex; -import eu.mihosoft.vrl.v3d.ext.org.poly2tri.PolygonUtil; import java.lang.foreign.MemorySegment; import java.util.ArrayList; @@ -12,12 +11,15 @@ import java.util.List; import com.cadoodlecad.manifold.ManifoldBindings; -import com.cadoodlecad.manifold.ManifoldBindings.MeshData64; +import com.cadoodlecad.manifold.ManifoldBindings.MeshData64; /** - * Imports a manifold3d mesh (via its native bridge API) into a JCSG {@link CSG} object. + * Imports a manifold3d mesh (via its native bridge API) into a JCSG {@link CSG} + * object. + * + *

+ * Usage: * - *

Usage: *

{@code
  * Manifold3dBridge bridge = ...; // your wrapper holding the MethodHandle map
  * MemorySegment manifoldSeg = bridge.importMeshGL64(vertices, triangles, nVerts, nTris);
@@ -25,14 +27,13 @@
  * CSG csg = new Manifold3dImporter(bridge).fromManifold(manifoldSeg);
  * }
* - *

The bridge instance must expose {@code exportMeshGL64(MemorySegment)} returning a - * {@code MeshData64} record with fields {@code double[] vertices}, {@code long[] triangles}, - * {@code int vertCount}, and {@code int triCount}. + *

+ * The bridge instance must expose {@code exportMeshGL64(MemorySegment)} + * returning a {@code MeshData64} record with fields {@code double[] vertices}, + * {@code long[] triangles}, {@code int vertCount}, and {@code int triCount}. */ public class Manifold3dImporter { - - private ManifoldBindings bridge; public Manifold3dImporter(ManifoldBindings bridge) { @@ -44,15 +45,20 @@ public Manifold3dImporter(ManifoldBindings bridge) { /** * Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}. * - *

The manifold is exported as a triangle soup via the bridge's {@code exportMeshGL64} - * method. Each triangle becomes one JCSG {@link Polygon} (three {@link Vertex} objects - * with positions taken from the flat vertex array). Per-vertex normals are computed - * as the face normal so that JCSG downstream tools (BSP, boolean ops) have valid planes. + *

+ * The manifold is exported as a triangle soup via the bridge's + * {@code exportMeshGL64} method. Each triangle becomes one JCSG {@link Polygon} + * (three {@link Vertex} objects with positions taken from the flat vertex + * array). Per-vertex normals are computed as the face normal so that JCSG + * downstream tools (BSP, boolean ops) have valid planes. * - * @param manifold native manifold segment returned by the bridge import call + * @param manifold + * native manifold segment returned by the bridge import call * @return a new {@link CSG} representing the same geometry - * @throws Throwable if the native export call fails - * @throws IllegalArgumentException if {@code manifold} is null + * @throws Throwable + * if the native export call fails + * @throws IllegalArgumentException + * if {@code manifold} is null */ public CSG fromManifold(MemorySegment manifold) throws Throwable { if (manifold == null) @@ -96,4 +102,4 @@ private static Vector3d vertexAt(double[] verts, int index) { int base = index * 3; return new Vector3d(verts[base], verts[base + 1], verts[base + 2]); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 828234f3..a8fd89cb 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -63,7 +63,6 @@ import com.aparapi.Kernel; import com.aparapi.Range; import com.aparapi.internal.kernel.KernelRunner; -import com.cadoodlecad.manifold.ManifoldBindings; import com.neuronrobotics.interaction.CadInteractionEvent; import com.neuronrobotics.manifold3d.CSGManifold3d; @@ -2821,8 +2820,8 @@ public List slice(CSG incoming, Transform slicePlane, double normalInse e.printStackTrace(); optType = defaultOptType; } - }else { - Slice.setSliceEngine(null ); + } else { + Slice.setSliceEngine(null); Slice.getSliceEngine();// set the default when the engine is null } defaultOptType = optType; diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Slice.java b/src/main/java/eu/mihosoft/vrl/v3d/Slice.java index 79015c86..66482055 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Slice.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Slice.java @@ -419,7 +419,7 @@ boolean withinAPix(int[] incoming, int[] out) { } }; - private static ISlice sliceEngine ; + private static ISlice sliceEngine; /** * Returns true if this polygon lies entirely in the z plane @@ -498,8 +498,8 @@ public static List slice(CSG incoming, double normalInsetDistance) thro return slice(incoming, new Transform(), normalInsetDistance); } public static ISlice getSliceEngine() { - if(sliceEngine==null) - sliceEngine= new DefaultSliceImp(); + if (sliceEngine == null) + sliceEngine = new DefaultSliceImp(); return sliceEngine; } From 1bb00d0bb668a42aac7c56a4ede0f1602b3977e7 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Tue, 7 Apr 2026 17:43:48 -0400 Subject: [PATCH 28/44] new version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 314c0d13..688b3c9e 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:v3.4.1-2") + implementation("com.github.madhephaestus:manifold3d:v3.4.1-5") } @@ -126,7 +126,7 @@ tasks.withType(JavaExec).configureEach { jvmArgs '--enable-preview' } test { - dependsOn 'spotlessCheck' + dependsOn 'spotlessApply' testLogging { // Show which test is running events "passed", "skipped", "failed", "standardOut", "standardError" From 9172e2b5c6e38a0e94a75dab74aabf6151a7bf49 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Wed, 8 Apr 2026 18:57:47 -0400 Subject: [PATCH 29/44] adding the unit tested version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 688b3c9e..b1940ec2 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:v3.4.1-5") + implementation("com.github.madhephaestus:manifold3d:v3.4.1-8") } From 5e6bcf259ef1241189e3a1c5ab328407f4150a60 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Wed, 8 Apr 2026 18:58:35 -0400 Subject: [PATCH 30/44] adding orentation --- .../java/com/neuronrobotics/manifold3d/CSGManifold3d.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java index 21523445..655e1a63 100644 --- a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java +++ b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java @@ -8,6 +8,7 @@ import eu.mihosoft.vrl.v3d.CSG; import eu.mihosoft.vrl.v3d.Polygon; +import eu.mihosoft.vrl.v3d.Transform; import eu.mihosoft.vrl.v3d.Vector3d; import eu.mihosoft.vrl.v3d.Vertex; @@ -83,7 +84,8 @@ private CSG fromManifold(MemorySegment manifold) throws Throwable { * @throws RuntimeException * wrapping any native call failure */ - public ArrayList sliceAtZero(CSG csg) { + public ArrayList sliceAtZero(CSG incoming, Transform slicePlane) { + CSG csg = incoming.transformed(slicePlane.inverse()); try { MemorySegment csgm = toManifold(csg); List contours = manifold.slice(csgm, 0.0); From d644de2a74c4e4a9f575b863b6321d86f7b23029 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Wed, 8 Apr 2026 18:58:59 -0400 Subject: [PATCH 31/44] add the interface to manifold --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 26 ++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index a8fd89cb..9c128f0c 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -894,7 +894,12 @@ public CSG union(CSG csg) { case POLYGON_BOUND : return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); case Manifold3d : - new RuntimeException("Not implemented yet").printStackTrace(); + try { + return manifold.union(this, csg); + } catch (Throwable e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } default : // return _unionIntersectOpt(csg); return _unionNoOpt(csg).historySync(this).historySync(csg); @@ -1420,7 +1425,12 @@ public CSG difference(CSG csg) { case POLYGON_BOUND : return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); case Manifold3d : - new RuntimeException("Not implemented yet").printStackTrace(); + try { + return manifold.difference(this, csg); + } catch (Throwable e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } default : return _differenceNoOpt(csg).historySync(this).historySync(csg); @@ -1594,7 +1604,12 @@ public CSG intersect(CSG csg) { return CSG.fromPolygons(new ArrayList()).historySync(this).historySync(csg); } if (defaultOptType == OptType.Manifold3d) { - new RuntimeException("Manifold3d not implemented here").printStackTrace(); + try { + return manifold.intersection(this, csg); + } catch (Throwable e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } Node a; @@ -2807,16 +2822,13 @@ public static void setDefaultOptType(OptType optType) { try { manifold = new CSGManifold3d(); Slice.setSliceEngine(new ISlice() { - @Override public List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) throws ColinearPointsException { - new RuntimeException("Manifold3d not implemented yet").printStackTrace(); - return new ArrayList(); + return manifold.sliceAtZero(incoming, slicePlane); } }); } catch (Exception e) { - // TODO Auto-generated catch block e.printStackTrace(); optType = defaultOptType; } From f02864da7315cf8a9ee8fcbf576099b4ac659bc8 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Thu, 9 Apr 2026 14:21:47 -0400 Subject: [PATCH 32/44] unit tests using the JCSG api for operations --- Manifold-SVGExportTest.svg | 1425 +++++++++++++++++ .../manifold3d/CSGManifold3d.java | 303 ++-- .../manifold3d/Manifold3dExporter.java | 142 -- .../manifold3d/Manifold3dImporter.java | 105 -- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 12 +- .../vrl/v3d/ext/quickhull3d/HullUtil.java | 7 +- .../eu/mihosoft/vrl/v3d/Manifold3d_test.java | 22 +- 7 files changed, 1609 insertions(+), 407 deletions(-) create mode 100644 Manifold-SVGExportTest.svg delete mode 100644 src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java delete mode 100644 src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java diff --git a/Manifold-SVGExportTest.svg b/Manifold-SVGExportTest.svg new file mode 100644 index 00000000..0793552c --- /dev/null +++ b/Manifold-SVGExportTest.svg @@ -0,0 +1,1425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java index 655e1a63..14a7d345 100644 --- a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java +++ b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java @@ -2,27 +2,33 @@ import java.lang.foreign.MemorySegment; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.cadoodlecad.manifold.ManifoldBindings; +import com.cadoodlecad.manifold.ManifoldBindings.MeshData64; import eu.mihosoft.vrl.v3d.CSG; import eu.mihosoft.vrl.v3d.Polygon; import eu.mihosoft.vrl.v3d.Transform; import eu.mihosoft.vrl.v3d.Vector3d; import eu.mihosoft.vrl.v3d.Vertex; +import eu.mihosoft.vrl.v3d.ext.org.poly2tri.PolygonUtil; public class CSGManifold3d { private final ManifoldBindings manifold; - private final Manifold3dExporter exporter; - private final Manifold3dImporter importer; + // private final Manifold3dExporter exporter; + // private final Manifold3dImporter importer; public CSGManifold3d() throws Exception { this.manifold = new ManifoldBindings(); - exporter = new Manifold3dExporter(manifold); - importer = new Manifold3dImporter(manifold); + // exporter = new Manifold3dExporter(manifold); + // importer = new Manifold3dImporter(manifold); } + /** * Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}. * @@ -38,10 +44,72 @@ public CSGManifold3d() throws Exception { * @throws IllegalArgumentException * if {@code csg} is null or has no polygons */ - private MemorySegment toManifold(CSG csg) throws Throwable { - return exporter.toManifold(csg); + public MemorySegment toManifold(CSG csg) throws Throwable { + if (csg == null) + throw new IllegalArgumentException("csg must not be null"); + + List polygons = csg.getPolygons(); + if (polygons == null || polygons.isEmpty()) + throw new IllegalArgumentException("CSG has no polygons"); + + // Build an indexed triangle mesh. + // Use a tolerance-free exact key so we don't merge + // numerically-close-but-distinct verts. + Map vertexIndex = new HashMap<>(); + List vertexList = new ArrayList<>(); + List triList = new ArrayList<>(); + + for (Polygon incoming : polygons) { + for (Polygon poly : PolygonUtil.triangulatePolygon(incoming)) { + List pverts = poly.getVertices(); + if (pverts == null || pverts.size() < 3) + continue; + + // Fan triangulation: (0,1,2), (0,2,3), (0,3,4), ... + int i0 = intern(pverts.get(0), vertexIndex, vertexList); + for (int i = 1; i < pverts.size() - 1; i++) { + int i1 = intern(pverts.get(i), vertexIndex, vertexList); + int i2 = intern(pverts.get(i + 1), vertexIndex, vertexList); + + // Skip degenerate triangles (two or more identical indices). + if (i0 == i1 || i1 == i2 || i0 == i2) + continue; + + triList.add((long) i0); + triList.add((long) i1); + triList.add((long) i2); + } + } + } + + if (triList.isEmpty()) + throw new IllegalArgumentException("CSG produced no valid triangles after triangulation"); + + long nVerts = vertexList.size(); + long nTris = triList.size() / 3; + + // Flatten vertex list into a primitive array. + double[] vertices = new double[(int) (nVerts * 3)]; + for (int i = 0; i < nVerts; i++) { + double[] v = vertexList.get(i); + vertices[i * 3] = v[0]; + vertices[i * 3 + 1] = v[1]; + vertices[i * 3 + 2] = v[2]; + } + + // Flatten triangle index list. + long[] triangles = new long[triList.size()]; + for (int i = 0; i < triList.size(); i++) { + triangles[i] = triList.get(i); + } + + return manifold.importMeshGL64(vertices, triangles, nVerts, nTris); } + // ------------------------------------------------------------------------- + // helpers + + /** * Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}. * @@ -52,7 +120,7 @@ private MemorySegment toManifold(CSG csg) throws Throwable { * array). Per-vertex normals are computed as the face normal so that JCSG * downstream tools (BSP, boolean ops) have valid planes. * - * @param manifold + * @param ms * native manifold segment returned by the bridge import call * @return a new {@link CSG} representing the same geometry * @throws Throwable @@ -60,8 +128,58 @@ private MemorySegment toManifold(CSG csg) throws Throwable { * @throws IllegalArgumentException * if {@code manifold} is null */ - private CSG fromManifold(MemorySegment manifold) throws Throwable { - return importer.fromManifold(manifold); + public CSG fromManifold(MemorySegment ms) throws Throwable { + if (ms == null) + throw new IllegalArgumentException("manifold segment must not be null"); + + MeshData64 mesh = this.manifold.exportMeshGL64(ms); + + double[] verts = mesh.vertices(); // flat [x0,y0,z0, x1,y1,z1, ...] + long[] tris = mesh.triangles(); // flat [i0,i1,i2, i3,i4,i5, ...] + int triCount = mesh.triCount(); + + if (triCount == 0) + return new CSG(); + + ArrayList polygons = new ArrayList<>(triCount); + + for (int t = 0; t < triCount; t++) { + int base = t * 3; + + Vector3d p0 = vertexAt(verts, (int) tris[base]); + Vector3d p1 = vertexAt(verts, (int) tris[base + 1]); + Vector3d p2 = vertexAt(verts, (int) tris[base + 2]); + + List vertices = Arrays.asList(new Vertex(p0), new Vertex(p1), new Vertex(p2)); + + polygons.add(new Polygon(vertices)); + } + + return CSG.fromPolygons(polygons); + } + + // ------------------------------------------------------------------------- + // helpers + /** + * Returns the index of {@code v} in {@code vertexList}, inserting it if not + * already present. The key is an exact string representation of (x, y, z) using + * {@link Double#toHexString} so that only bit-identical positions are merged, + * matching the BSP's behavior. + */ + private static int intern(Vertex v, Map index, List list) { + String key = Double.toHexString(v.pos.x) + "," + Double.toHexString(v.pos.y) + "," + + Double.toHexString(v.pos.z); + + return index.computeIfAbsent(key, k -> { + int idx = list.size(); + list.add(new double[] { v.pos.x, v.pos.y, v.pos.z }); + return idx; + }); + } + + private static Vector3d vertexAt(double[] verts, int index) { + int base = index * 3; + return new Vector3d(verts[base], verts[base + 1], verts[base + 2]); } /** @@ -131,8 +249,8 @@ public CSG union(CSG a, CSG b) throws Throwable { MemorySegment result = manifold.union(ma, mb); return fromManifold(result); } finally { - manifold.deleteMeshGL64(ma); - manifold.deleteMeshGL64(mb); + manifold.delete(ma); + manifold.delete(mb); } } @@ -147,8 +265,8 @@ public CSG difference(CSG a, CSG b) throws Throwable { MemorySegment result = manifold.difference(ma, mb); return fromManifold(result); } finally { - manifold.deleteMeshGL64(ma); - manifold.deleteMeshGL64(mb); + manifold.delete(ma); + manifold.delete(mb); } } @@ -163,8 +281,8 @@ public CSG intersection(CSG a, CSG b) throws Throwable { MemorySegment result = manifold.intersection(ma, mb); return fromManifold(result); } finally { - manifold.deleteMeshGL64(ma); - manifold.deleteMeshGL64(mb); + manifold.delete(ma); + manifold.delete(mb); } } @@ -185,7 +303,7 @@ public CSG hull(CSG a) throws Throwable { MemorySegment result = manifold.hull(ma); return fromManifold(result); } finally { - manifold.deleteMeshGL64(ma); + manifold.delete(ma); } } @@ -207,144 +325,21 @@ public CSG hull(CSG... solids) throws Throwable { } } - // ------------------------------------------------------------------------- - // Slice at a plane → List - // ------------------------------------------------------------------------- - - // /** - // * Slices a CSG solid at a horizontal plane (Z = height) and returns the - // * resulting cross-section contours as JCSG {@link Polygon}s. - // *

- // * Manifold's {@code manifold_slice(mem, m, height)} always cuts perpendicular - // * to the Z axis and returns a {@code ManifoldPolygons*}. Each contour ring - // * is reconstructed here as a JCSG {@link Polygon} lying in the XY plane at - // * {@code z = height}. - // *

- // * To cut along an arbitrary plane, rotate the solid so that the desired - // * normal aligns with +Z, call this method, then reverse-rotate the polygons. - // * - // * @param csg the solid to slice - // * @param height Z coordinate of the cutting plane - // * @return cross-section polygons (may be empty for solids that don't reach - // * that height) - // * @throws Throwable if the native call fails - // */ - // public List sliceAtZ(CSG csg, double height) throws Throwable { - // MemorySegment m = toManifold(csg); - // try { - // // manifold.slice(MemorySegment m, double height) → - // // ManifoldPolygons* manifold_slice(void* mem, ManifoldManifold* m, double - // height) - // MemorySegment polygonsSeg = manifold.slice(m, height); - // try { - // return importer.fromManifoldPolygons(polygonsSeg, height); - // } finally { - // manifold.deletePolygons(polygonsSeg); - // } - // } finally { - // manifold.deleteMeshGL64(m); - // } - // } - - // ------------------------------------------------------------------------- - // STL import / export - // ------------------------------------------------------------------------- + public CSG hull(List points) throws Throwable { + ArrayList pts = new ArrayList(); + for (int i = 0; i < points.size(); i++) { + Vector3d v = points.get(i); + double[] p = new double[] { v.x, v.y, v.z }; + pts.add(p); + } + MemorySegment mem = null; + try { + mem = manifold.hullPoints(pts); + return fromManifold(mem); + } finally { + manifold.delete(mem); + } + } - // /** - // * Exports a CSG solid to an STL file via Manifold's mesh pipeline. - // *

- // * The geometry is round-tripped through Manifold's MeshGL64 representation - // * and written by the {@link Manifold3dSTLExporter} helper. - // *

- // * Note: STL is lossy – it has no topology encoding. Prefer 3MF for - // * any round-trip use-case. - // * - // * @param csg the solid to export - // * @param file destination STL file (created or overwritten) - // * @throws Throwable if export or file-write fails - // */ - // public void exportSTL(CSG csg, File file) throws Throwable { - // MemorySegment m = toManifold(csg); - // try { - // ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m); - // Manifold3dSTLExporter.write(mesh, file); - // } finally { - // manifold.deleteMeshGL64(m); - // } - // } - // - // /** - // * Imports an STL file and returns its geometry as a CSG solid. - // *

- // * The file is parsed by {@link Manifold3dSTLImporter} into raw - // vertex/triangle - // * arrays that are fed into {@code manifold.importMeshGL64(...)}, which merges - // * duplicate vertices and validates manifoldness before handing back a native - // * manifold segment that is then converted to CSG. - // * - // * @param file the STL file to read - // * @return a new {@link CSG} representing the imported geometry - // * @throws Throwable if parsing or the native import fails - // * @throws IllegalArgumentException if the file does not exist - // */ - // public CSG importSTL(File file) throws Throwable { - // if (!file.exists()) - // throw new IllegalArgumentException("STL file not found: " + - // file.getAbsolutePath()); - // Manifold3dSTLImporter.RawMesh raw = Manifold3dSTLImporter.read(file); - // MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), - // raw.vertCount(), raw.triCount()); - // try { - // return fromManifold(m); - // } finally { - // manifold.deleteMeshGL64(m); - // } - // } - // - // // ------------------------------------------------------------------------- - // // 3MF import / export - // // ------------------------------------------------------------------------- - // - // /** - // * Exports a CSG solid to a 3MF file. - // *

- // * 3MF preserves topology and is lossless – recommended over STL for any - // * workflow that re-imports the file. - // * - // * @param csg the solid to export - // * @param file destination 3MF file (created or overwritten) - // * @throws Throwable if export or file-write fails - // */ - // public void export3MF(CSG csg, File file) throws Throwable { - // MemorySegment m = toManifold(csg); - // try { - // ManifoldBindings.MeshData64 mesh = manifold.exportMeshGL64(m); - // Manifold3d3MFExporter.write(mesh, file); - // } finally { - // manifold.deleteMeshGL64(m); - // } - // } - // - // /** - // * Imports a 3MF file and returns the first body as a CSG solid. - // * - // * @param file the 3MF file to read - // * @return a new {@link CSG} representing the imported geometry - // * @throws Throwable if parsing or the native import fails - // * @throws IllegalArgumentException if the file does not exist - // */ - // public CSG import3MF(File file) throws Throwable { - // if (!file.exists()) - // throw new IllegalArgumentException("3MF file not found: " + - // file.getAbsolutePath()); - // Manifold3dSTLImporter.RawMesh raw = Manifold3d3MFImporter.read(file); - // MemorySegment m = manifold.importMeshGL64(raw.vertices(), raw.triangles(), - // raw.vertCount(), raw.triCount()); - // try { - // return fromManifold(m); - // } finally { - // manifold.deleteMeshGL64(m); - // } - // } } diff --git a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java deleted file mode 100644 index f306147e..00000000 --- a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dExporter.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.neuronrobotics.manifold3d; - -import java.lang.foreign.MemorySegment; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.cadoodlecad.manifold.ManifoldBindings; - -import eu.mihosoft.vrl.v3d.CSG; -import eu.mihosoft.vrl.v3d.Polygon; -import eu.mihosoft.vrl.v3d.Vertex; - -/** - * Exports a JCSG {@link CSG} object into a native manifold3d manifold via the - * bridge API. - * - *

- * Usage: - * - *

{@code
- * Manifold3dBridge bridge = ...; // your wrapper holding the MethodHandle map
- * CSG csg = ...;
- *
- * MemorySegment manifoldSeg = new Manifold3dExporter(bridge).toManifold(csg);
- * // use manifoldSeg with bridge boolean operations, then clean up via bridge.delete(...)
- * }
- * - *

- * JCSG polygons may have more than three vertices (the BSP representation - * preserves quads and n-gons). This exporter triangulates each polygon using a - * simple fan from the first vertex (safe for convex polygons, which is - * guaranteed by the JCSG BSP). The resulting triangle soup is de-duplicated - * into an indexed mesh before being handed to the bridge so that manifold3d's - * merge step has shared vertices to work with. - */ -public class Manifold3dExporter { - - private final ManifoldBindings bridge; - - public Manifold3dExporter(ManifoldBindings bridge) { - if (bridge == null) - throw new IllegalArgumentException("bridge must not be null"); - this.bridge = bridge; - } - - /** - * Converts a JCSG {@link CSG} into a native manifold {@link MemorySegment}. - * - *

- * The caller is responsible for eventually freeing the returned segment via the - * bridge's delete method (e.g. {@code manifold_delete_manifold}). - * - * @param csg - * the solid to export; must not be null - * @return a native manifold segment ready for boolean operations - * @throws Throwable - * if the native import call fails - * @throws IllegalArgumentException - * if {@code csg} is null or has no polygons - */ - public MemorySegment toManifold(CSG csg) throws Throwable { - if (csg == null) - throw new IllegalArgumentException("csg must not be null"); - - List polygons = csg.getPolygons(); - if (polygons == null || polygons.isEmpty()) - throw new IllegalArgumentException("CSG has no polygons"); - - // Build an indexed triangle mesh. - // Use a tolerance-free exact key so we don't merge - // numerically-close-but-distinct verts. - Map vertexIndex = new HashMap<>(); - List vertexList = new ArrayList<>(); - List triList = new ArrayList<>(); - - for (Polygon poly : polygons) { - List pverts = poly.getVertices(); - if (pverts == null || pverts.size() < 3) - continue; - - // Fan triangulation: (0,1,2), (0,2,3), (0,3,4), ... - int i0 = intern(pverts.get(0), vertexIndex, vertexList); - for (int i = 1; i < pverts.size() - 1; i++) { - int i1 = intern(pverts.get(i), vertexIndex, vertexList); - int i2 = intern(pverts.get(i + 1), vertexIndex, vertexList); - - // Skip degenerate triangles (two or more identical indices). - if (i0 == i1 || i1 == i2 || i0 == i2) - continue; - - triList.add((long) i0); - triList.add((long) i1); - triList.add((long) i2); - } - } - - if (triList.isEmpty()) - throw new IllegalArgumentException("CSG produced no valid triangles after triangulation"); - - long nVerts = vertexList.size(); - long nTris = triList.size() / 3; - - // Flatten vertex list into a primitive array. - double[] vertices = new double[(int) (nVerts * 3)]; - for (int i = 0; i < nVerts; i++) { - double[] v = vertexList.get(i); - vertices[i * 3] = v[0]; - vertices[i * 3 + 1] = v[1]; - vertices[i * 3 + 2] = v[2]; - } - - // Flatten triangle index list. - long[] triangles = new long[triList.size()]; - for (int i = 0; i < triList.size(); i++) { - triangles[i] = triList.get(i); - } - - return bridge.importMeshGL64(vertices, triangles, nVerts, nTris); - } - - // ------------------------------------------------------------------------- - // helpers - - /** - * Returns the index of {@code v} in {@code vertexList}, inserting it if not - * already present. The key is an exact string representation of (x, y, z) using - * {@link Double#toHexString} so that only bit-identical positions are merged, - * matching the BSP's behaviour. - */ - private static int intern(Vertex v, Map index, List list) { - String key = Double.toHexString(v.pos.x) + "," + Double.toHexString(v.pos.y) + "," - + Double.toHexString(v.pos.z); - - return index.computeIfAbsent(key, k -> { - int idx = list.size(); - list.add(new double[]{v.pos.x, v.pos.y, v.pos.z}); - return idx; - }); - } -} diff --git a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java b/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java deleted file mode 100644 index c5a0b7ab..00000000 --- a/src/main/java/com/neuronrobotics/manifold3d/Manifold3dImporter.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.neuronrobotics.manifold3d; - -import eu.mihosoft.vrl.v3d.CSG; -import eu.mihosoft.vrl.v3d.Polygon; -import eu.mihosoft.vrl.v3d.Vector3d; -import eu.mihosoft.vrl.v3d.Vertex; - -import java.lang.foreign.MemorySegment; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import com.cadoodlecad.manifold.ManifoldBindings; -import com.cadoodlecad.manifold.ManifoldBindings.MeshData64; - -/** - * Imports a manifold3d mesh (via its native bridge API) into a JCSG {@link CSG} - * object. - * - *

- * Usage: - * - *

{@code
- * Manifold3dBridge bridge = ...; // your wrapper holding the MethodHandle map
- * MemorySegment manifoldSeg = bridge.importMeshGL64(vertices, triangles, nVerts, nTris);
- *
- * CSG csg = new Manifold3dImporter(bridge).fromManifold(manifoldSeg);
- * }
- * - *

- * The bridge instance must expose {@code exportMeshGL64(MemorySegment)} - * returning a {@code MeshData64} record with fields {@code double[] vertices}, - * {@code long[] triangles}, {@code int vertCount}, and {@code int triCount}. - */ -public class Manifold3dImporter { - - private ManifoldBindings bridge; - - public Manifold3dImporter(ManifoldBindings bridge) { - if (bridge == null) - throw new IllegalArgumentException("bridge must not be null"); - this.bridge = bridge; - } - - /** - * Converts a native manifold {@link MemorySegment} to a JCSG {@link CSG}. - * - *

- * The manifold is exported as a triangle soup via the bridge's - * {@code exportMeshGL64} method. Each triangle becomes one JCSG {@link Polygon} - * (three {@link Vertex} objects with positions taken from the flat vertex - * array). Per-vertex normals are computed as the face normal so that JCSG - * downstream tools (BSP, boolean ops) have valid planes. - * - * @param manifold - * native manifold segment returned by the bridge import call - * @return a new {@link CSG} representing the same geometry - * @throws Throwable - * if the native export call fails - * @throws IllegalArgumentException - * if {@code manifold} is null - */ - public CSG fromManifold(MemorySegment manifold) throws Throwable { - if (manifold == null) - throw new IllegalArgumentException("manifold segment must not be null"); - - MeshData64 mesh = bridge.exportMeshGL64(manifold); - - double[] verts = mesh.vertices(); // flat [x0,y0,z0, x1,y1,z1, ...] - long[] tris = mesh.triangles(); // flat [i0,i1,i2, i3,i4,i5, ...] - int triCount = mesh.triCount(); - - if (triCount == 0) - return new CSG(); - - ArrayList polygons = new ArrayList<>(triCount); - - for (int t = 0; t < triCount; t++) { - int base = t * 3; - - Vector3d p0 = vertexAt(verts, (int) tris[base]); - Vector3d p1 = vertexAt(verts, (int) tris[base + 1]); - Vector3d p2 = vertexAt(verts, (int) tris[base + 2]); - - // Face normal (used as the vertex normal for all three corners). - Vector3d edge1 = p1.minus(p0); - Vector3d edge2 = p2.minus(p0); - Vector3d normal = edge1.cross(edge2).normalized(); - - List vertices = Arrays.asList(new Vertex(p0), new Vertex(p1), new Vertex(p2)); - - polygons.add(new Polygon(vertices)); - } - - return CSG.fromPolygons(polygons); - } - - // ------------------------------------------------------------------------- - // helpers - - private static Vector3d vertexAt(double[] verts, int index) { - int base = index * 3; - return new Vector3d(verts[base], verts[base + 1], verts[base + 2]); - } -} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 9c128f0c..813dd95f 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -895,7 +895,7 @@ public CSG union(CSG csg) { return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); case Manifold3d : try { - return manifold.union(this, csg); + return getManifold().union(this, csg); } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -1426,7 +1426,7 @@ public CSG difference(CSG csg) { return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); case Manifold3d : try { - return manifold.difference(this, csg); + return getManifold().difference(this, csg); } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -1605,7 +1605,7 @@ public CSG intersect(CSG csg) { } if (defaultOptType == OptType.Manifold3d) { try { - return manifold.intersection(this, csg); + return getManifold().intersection(this, csg); } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -2825,7 +2825,7 @@ public static void setDefaultOptType(OptType optType) { @Override public List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) throws ColinearPointsException { - return manifold.sliceAtZero(incoming, slicePlane); + return getManifold().sliceAtZero(incoming, slicePlane); } }); } catch (Exception e) { @@ -4358,4 +4358,8 @@ public static OptType getDefaultOptionType() { return defaultOptType; } + public static CSGManifold3d getManifold() { + return manifold; + } + } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java index 364a0f49..a5f52670 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/ext/quickhull3d/HullUtil.java @@ -73,7 +73,12 @@ public static CSG hull(List points, PropertyStorage storage) { } } if (CSG.getDefaultOptionType() == OptType.Manifold3d) { - new RuntimeException("Not implemented yet").printStackTrace(); + try { + return CSG.getManifold().hull(points); + } catch (Throwable e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } Point3d[] hullPoints = points.stream().map((vec) -> new Point3d(vec.x, vec.y, vec.z)).toArray(Point3d[]::new); diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index 6f1f4d09..c10c538e 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -1,8 +1,13 @@ package eu.mihosoft.vrl.v3d; +import java.io.File; +import java.nio.file.Paths; +import java.util.List; + import org.junit.Test; import eu.mihosoft.vrl.v3d.CSG.OptType; +import eu.mihosoft.vrl.v3d.svg.SVGExporter; public class Manifold3d_test { @Test @@ -11,7 +16,22 @@ public void loadTest() throws Throwable { try { CSG.setDefaultOptType(OptType.Manifold3d); - + CSG cube = new Cube(50,50,50).toCSG(); + CSG sphere = new Sphere(25, 10, 10).toCSG(); + List polygons = Slice.slice(sphere, new Transform(), 0); + SVGExporter.export(polygons, new File("Manifold-SVGExportTest.svg"), false); + CSG difference = cube.difference(sphere); + CSG intersect = cube.intersect(sphere); + CSG union = cube.union(sphere); + FileUtil.write(Paths.get("Manifole-union.stl"), + union.toStlString()); + FileUtil.write(Paths.get("Manifole-difference.stl"), + difference.toStlString()); + FileUtil.write(Paths.get("Manifole-intersect.stl"), + intersect.toStlString()); + CSG hull = union.hull(); + FileUtil.write(Paths.get("Manifole-hull.stl"), + hull.toStlString()); } catch (Throwable t) { t.printStackTrace(); // Set back to default to complete test and not disrupt other tests From dd4bd0757f3d665da57f3162768d17a726fb875f Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 10 Apr 2026 08:35:38 -0400 Subject: [PATCH 33/44] update bindings --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b1940ec2..fdeb351e 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:v3.4.1-8") + implementation("com.github.madhephaestus:manifold3d:v3.4.1-10") } From ff94975dc45679709ec946585db46a67c37b6158 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 10 Apr 2026 08:35:56 -0400 Subject: [PATCH 34/44] attempt to make the sphere manifold --- src/main/java/eu/mihosoft/vrl/v3d/Sphere.java | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java b/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java index 971a8484..07a4ecab 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java @@ -86,6 +86,7 @@ public Sphere(double radius) { init(); this.radius = radius; } + // public Cube(LengthParameter w, LengthParameter h, LengthParameter d) { // this(Vector3d.ZERO, new Vector3d(w.getMM(), h.getMM(), d.getMM())); // @@ -176,33 +177,35 @@ public List toPolygons() { if (radius <= 0) throw new NumberFormatException("radius can not be negative"); List polygons = new ArrayList<>(); + for (int i = 0; i < numSlices; i++) { + for (int j = 0; j < numStacks; j++) { - for (int i = 0; i < getNumSlices(); i++) { - for (int j = 0; j < getNumStacks(); j++) { - final List vertices = new ArrayList<>(); + Vertex v0 = sphereVertex(center, radius, i / (double) numSlices, j / (double) numStacks); + Vertex v1 = sphereVertex(center, radius, (i + 1) / (double) numSlices, j / (double) numStacks); + Vertex v2 = sphereVertex(center, radius, (i + 1) / (double) numSlices, (j + 1) / (double) numStacks); + Vertex v3 = sphereVertex(center, radius, i / (double) numSlices, (j + 1) / (double) numStacks); - vertices.add(sphereVertex(center, radius, i / (double) getNumSlices(), j / (double) getNumStacks())); - if (j > 0) { - vertices.add(sphereVertex(center, radius, (i + 1) / (double) getNumSlices(), - j / (double) getNumStacks())); - } - if (j < getNumStacks() - 1) { - vertices.add(sphereVertex(center, radius, (i + 1) / (double) getNumSlices(), - (j + 1) / (double) getNumStacks())); - } - vertices.add( - sphereVertex(center, radius, i / (double) getNumSlices(), (j + 1) / (double) getNumStacks())); - try { - polygons.add(new Polygon(vertices, getProperties())); - } catch (ColinearPointsException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + if (j == 0) { + addPolygon(polygons, v0, v3, v2); + } else if (j == numStacks - 1) { + addPolygon(polygons, v0, v2, v1); + } else { + addPolygon(polygons, v0, v3, v1); + addPolygon(polygons, v1, v3, v2); } } } return polygons; } + private void addPolygon(List polygons, Vertex... verts) { + try { + polygons.add(new Polygon(List.of(verts), getProperties())); + } catch (ColinearPointsException e) { + e.printStackTrace(); + } + } + /** * Gets the center. * From b971b497b09b83b6ee33931b9db61801afc797c0 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 10 Apr 2026 08:36:22 -0400 Subject: [PATCH 35/44] run the manifold ops first, fail over to Java --- src/main/java/eu/mihosoft/vrl/v3d/CSG.java | 86 ++++++++++++---------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index 813dd95f..ec3d2548 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -889,20 +889,21 @@ public CSG union(CSG csg) { // triangulate(); // csg.triangulate(); switch (getOptType()) { - case CSG_BOUND : - return _unionCSGBoundsOpt(csg).historySync(this).historySync(csg); - case POLYGON_BOUND : - return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); - case Manifold3d : + case Manifold3d: try { return getManifold().union(this, csg); } catch (Throwable e) { - // TODO Auto-generated catch block + System.err.println("ERROR failing over to Java Union "+e.getMessage()); e.printStackTrace(); } - default : - // return _unionIntersectOpt(csg); - return _unionNoOpt(csg).historySync(this).historySync(csg); + case CSG_BOUND: + return _unionCSGBoundsOpt(csg).historySync(this).historySync(csg); + case POLYGON_BOUND: + return _unionPolygonBoundsOpt(csg).historySync(this).historySync(csg); + + default: + // return _unionIntersectOpt(csg); + return _unionNoOpt(csg).historySync(this).historySync(csg); } } @@ -1420,19 +1421,20 @@ public CSG difference(CSG csg) { // polygons if (this.getPolygons().size() > 0 && csg.getPolygons().size() > 0) { switch (getOptType()) { - case CSG_BOUND : - return _differenceCSGBoundsOpt(csg).historySync(this).historySync(csg); - case POLYGON_BOUND : - return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); - case Manifold3d : + case Manifold3d: try { return getManifold().difference(this, csg); } catch (Throwable e) { - // TODO Auto-generated catch block + System.err.println("ERROR failing over to Java Difference "+e.getMessage()); e.printStackTrace(); } - default : - return _differenceNoOpt(csg).historySync(this).historySync(csg); + case CSG_BOUND: + return _differenceCSGBoundsOpt(csg).historySync(this).historySync(csg); + case POLYGON_BOUND: + return _differencePolygonBoundsOpt(csg).historySync(this).historySync(csg); + + default: + return _differenceNoOpt(csg).historySync(this).historySync(csg); } } else @@ -1447,16 +1449,16 @@ public CSG difference(CSG csg) { if (intersectingParts.getPolygons().size() > 0) { switch (getOptType()) { - case CSG_BOUND : - return _differenceCSGBoundsOpt(intersectingParts).historySync(this) - .historySync(intersectingParts); - case POLYGON_BOUND : - return _differencePolygonBoundsOpt(intersectingParts).historySync(this) - .historySync(intersectingParts); - case Manifold3d : - new RuntimeException("Not implemented yet").printStackTrace(); - default : - return _differenceNoOpt(intersectingParts).historySync(this).historySync(intersectingParts); + case CSG_BOUND: + return _differenceCSGBoundsOpt(intersectingParts).historySync(this) + .historySync(intersectingParts); + case POLYGON_BOUND: + return _differencePolygonBoundsOpt(intersectingParts).historySync(this) + .historySync(intersectingParts); + case Manifold3d: + new RuntimeException("Not implemented yet").printStackTrace(); + default: + return _differenceNoOpt(intersectingParts).historySync(this).historySync(intersectingParts); } } else return this; @@ -1607,7 +1609,7 @@ public CSG intersect(CSG csg) { try { return getManifold().intersection(this, csg); } catch (Throwable e) { - // TODO Auto-generated catch block + System.err.println("ERROR failing over to Java Intersect "+e.getMessage()); e.printStackTrace(); } } @@ -1742,6 +1744,7 @@ public CSG to3mf(File target) { } return this; } + public static CSG loadFrom3mf(File target) { if (defaultOptType == OptType.Manifold3d) { new RuntimeException("Manifold3d 3mf export not implemented yet").printStackTrace(); @@ -1750,6 +1753,7 @@ public static CSG loadFrom3mf(File target) { } return null; } + /** * Returns this csg in STL string format. * @@ -1759,9 +1763,9 @@ public static CSG loadFrom3mf(File target) { * @return the specified string builder */ public StringBuilder toStlString(StringBuilder sb) { - if (defaultOptType == OptType.Manifold3d) { - new RuntimeException("Manifold3d STL export not implemented yet").printStackTrace(); - } +// if (defaultOptType == OptType.Manifold3d) { +// new RuntimeException("Manifold3d STL export not implemented yet").printStackTrace(); +// } triangulate(false); try { sb.append("solid v3d.csg\n"); @@ -1921,7 +1925,7 @@ private int runGPUMakeManifold(int iteration, int np, int longLength, int numPol int[] added = new int[numberOfPolygons]; int testPointChunk = 1000; int snapChunk = 1000; - int[] tp = new int[]{0, snapChunk}; + int[] tp = new int[] { 0, snapChunk }; // Aparapi-compatible kernel with flattened data Kernel snapPointsToDistance = new Kernel() { @@ -2267,7 +2271,7 @@ public static void gpuRun(int numberOfPoints, Kernel kernel, float[] done, Strin progressMoniter.progressUpdate(0, 100, "CPU mode " + valueOf, null); kernel.setExecutionMode(Kernel.EXECUTION_MODE.JTP); // Java Thread Pool } - int[] iteration = new int[]{0}; + int[] iteration = new int[] { 0 }; long begin = System.currentTimeMillis(); boolean print = false; @@ -2825,7 +2829,15 @@ public static void setDefaultOptType(OptType optType) { @Override public List slice(CSG incoming, Transform slicePlane, double normalInsetDistance) throws ColinearPointsException { - return getManifold().sliceAtZero(incoming, slicePlane); + try { + return getManifold().sliceAtZero(incoming, slicePlane); + } catch (Throwable e) { + System.err.println("Slice failed on manifold, using legacy slice"); + e.printStackTrace(); + Slice.setSliceEngine(null); + Slice.getSliceEngine();// set the default when the engine is null + return Slice.getSliceEngine().slice(incoming,slicePlane,normalInsetDistance); + } } }); } catch (Exception e) { @@ -3369,7 +3381,7 @@ public static Color getDefaultColor() { public CSG getBoundingBox() { return new Cube((-this.getMinX() + this.getMaxX()), (-this.getMinY() + this.getMaxY()), (-this.getMinZ() + this.getMaxZ())).toCSG().toXMax().movex(this.getMaxX()).toYMax() - .movey(this.getMaxY()).toZMax().movez(this.getMaxZ()); + .movey(this.getMaxY()).toZMax().movez(this.getMaxZ()); } public String getName() { @@ -4140,8 +4152,8 @@ public static List tessellate(CSG incoming, int xSteps, int ySteps, int zSt public static List tessellate(CSG incoming, int xSteps, int ySteps, int zSteps, double oddRowXOffset, double oddRowYOffset, double oddRowZOffset, double oddColXOffset, double oddColYOffset, double oddColZOffset, double oddLayXOffset, double oddLayYOffset, double oddLayZOffset) { - double[][] offsets = {{oddRowXOffset, oddRowYOffset, oddRowZOffset}, - {oddColXOffset, oddColYOffset, oddColZOffset}, {oddLayXOffset, oddLayYOffset, oddLayZOffset}}; + double[][] offsets = { { oddRowXOffset, oddRowYOffset, oddRowZOffset }, + { oddColXOffset, oddColYOffset, oddColZOffset }, { oddLayXOffset, oddLayYOffset, oddLayZOffset } }; return tessellate(incoming, xSteps, ySteps, zSteps, incoming.getTotalX(), incoming.getTotalY(), incoming.getTotalZ(), offsets); } From 742685efd7b8742c2dd83e4c1309931d53186667 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 10 Apr 2026 08:36:35 -0400 Subject: [PATCH 36/44] error for non-manifold --- .../manifold3d/NonManifoldShapeError.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/neuronrobotics/manifold3d/NonManifoldShapeError.java diff --git a/src/main/java/com/neuronrobotics/manifold3d/NonManifoldShapeError.java b/src/main/java/com/neuronrobotics/manifold3d/NonManifoldShapeError.java new file mode 100644 index 00000000..802df317 --- /dev/null +++ b/src/main/java/com/neuronrobotics/manifold3d/NonManifoldShapeError.java @@ -0,0 +1,11 @@ +package com.neuronrobotics.manifold3d; + +public class NonManifoldShapeError extends Exception { + + private static final long serialVersionUID = 9171271069943603762L; + + public NonManifoldShapeError(String string) { + super(string); + } + +} From 0df898b6e47caf56bf07a846a316f1162f62c392 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 10 Apr 2026 08:38:03 -0400 Subject: [PATCH 37/44] add the manifold check to the loading --- .../manifold3d/CSGManifold3d.java | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java index 14a7d345..b6a30744 100644 --- a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java +++ b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java @@ -8,6 +8,7 @@ import java.util.Map; import com.cadoodlecad.manifold.ManifoldBindings; +import com.cadoodlecad.manifold.ManifoldBindings.ManifoldError; import com.cadoodlecad.manifold.ManifoldBindings.MeshData64; import eu.mihosoft.vrl.v3d.CSG; @@ -103,7 +104,9 @@ public MemorySegment toManifold(CSG csg) throws Throwable { triangles[i] = triList.get(i); } - return manifold.importMeshGL64(vertices, triangles, nVerts, nTris); + MemorySegment ms = manifold.importMeshGL64(vertices, triangles, nVerts, nTris); + checkResult(ms); + return ms; } // ------------------------------------------------------------------------- @@ -131,7 +134,6 @@ public MemorySegment toManifold(CSG csg) throws Throwable { public CSG fromManifold(MemorySegment ms) throws Throwable { if (ms == null) throw new IllegalArgumentException("manifold segment must not be null"); - MeshData64 mesh = this.manifold.exportMeshGL64(ms); double[] verts = mesh.vertices(); // flat [x0,y0,z0, x1,y1,z1, ...] @@ -199,13 +201,16 @@ private static Vector3d vertexAt(double[] verts, int index) { * the solid to slice * @return closed polygon contours of the cross-section at Z=0, never * {@code null}, may be empty if the plane misses the solid + * @throws Throwable * @throws RuntimeException * wrapping any native call failure */ - public ArrayList sliceAtZero(CSG incoming, Transform slicePlane) { + public ArrayList sliceAtZero(CSG incoming, Transform slicePlane) throws Throwable { CSG csg = incoming.transformed(slicePlane.inverse()); + MemorySegment csgm = null; try { - MemorySegment csgm = toManifold(csg); + csgm = toManifold(csg); + checkResult(csgm); List contours = manifold.slice(csgm, 0.0); ArrayList result = new ArrayList<>(contours.size()); @@ -227,9 +232,9 @@ public ArrayList sliceAtZero(CSG incoming, Transform slicePlane) { return result; - } catch (RuntimeException e) { - throw e; } catch (Throwable e) { + if(csgm!=null) + manifold.delete(csgm); throw new RuntimeException("Failed to slice CSG at Z=0", e); } } @@ -247,6 +252,7 @@ public CSG union(CSG a, CSG b) throws Throwable { MemorySegment mb = toManifold(b); try { MemorySegment result = manifold.union(ma, mb); + checkResult(result); return fromManifold(result); } finally { manifold.delete(ma); @@ -263,6 +269,7 @@ public CSG difference(CSG a, CSG b) throws Throwable { MemorySegment mb = toManifold(b); try { MemorySegment result = manifold.difference(ma, mb); + checkResult(result); return fromManifold(result); } finally { manifold.delete(ma); @@ -279,6 +286,7 @@ public CSG intersection(CSG a, CSG b) throws Throwable { MemorySegment mb = toManifold(b); try { MemorySegment result = manifold.intersection(ma, mb); + checkResult(result); return fromManifold(result); } finally { manifold.delete(ma); @@ -299,8 +307,10 @@ public CSG intersection(CSG a, CSG b) throws Throwable { */ public CSG hull(CSG a) throws Throwable { MemorySegment ma = toManifold(a); + checkResult(ma); try { MemorySegment result = manifold.hull(ma); + checkResult(result); return fromManifold(result); } finally { manifold.delete(ma); @@ -314,17 +324,30 @@ public CSG hull(CSG a) throws Throwable { */ public CSG hull(CSG... solids) throws Throwable { MemorySegment[] segs = new MemorySegment[solids.length]; - for (int i = 0; i < solids.length; i++) + for (int i = 0; i < solids.length; i++) { segs[i] = toManifold(solids[i]); + } try { MemorySegment result = manifold.batchHull(segs); + checkResult(result); return fromManifold(result); } finally { for (MemorySegment seg : segs) - manifold.deleteMeshGL64(seg); + manifold.delete(seg); } } + private void checkResult(MemorySegment... memorySegments) throws Throwable { + for (int i = 0; i < memorySegments.length; i++) { + MemorySegment ms = memorySegments[i]; + ManifoldError result = manifold.status(ms); + //System.out.println("Status of Manifold Op is "+result); + if (result != ManifoldError.NO_ERROR) + throw new NonManifoldShapeError("Error was " + result); + } + } + + public CSG hull(List points) throws Throwable { ArrayList pts = new ArrayList(); for (int i = 0; i < points.size(); i++) { @@ -334,7 +357,9 @@ public CSG hull(List points) throws Throwable { } MemorySegment mem = null; try { - mem = manifold.hullPoints(pts); + mem = manifold.hull(pts); + checkResult(mem); + return fromManifold(mem); } finally { manifold.delete(mem); From f70feae78f7b94c6d1bab8525b5bcc4b2dd8cd29 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 10 Apr 2026 08:38:31 -0400 Subject: [PATCH 38/44] updating the test --- src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index c10c538e..4f86d4d4 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -17,12 +17,14 @@ public void loadTest() throws Throwable { try { CSG.setDefaultOptType(OptType.Manifold3d); CSG cube = new Cube(50,50,50).toCSG(); - CSG sphere = new Sphere(25, 10, 10).toCSG(); + CSG sphere = new Sphere(30, 10, 10).toCSG(); List polygons = Slice.slice(sphere, new Transform(), 0); SVGExporter.export(polygons, new File("Manifold-SVGExportTest.svg"), false); CSG difference = cube.difference(sphere); CSG intersect = cube.intersect(sphere); CSG union = cube.union(sphere); + FileUtil.write(Paths.get("Manifole-sphere.stl"), + sphere.toStlString()); FileUtil.write(Paths.get("Manifole-union.stl"), union.toStlString()); FileUtil.write(Paths.get("Manifole-difference.stl"), From ddde700680eb3311e6d5f41e02086129d53d62a5 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Fri, 10 Apr 2026 08:39:09 -0400 Subject: [PATCH 39/44] remove the svg from version control --- .gitignore | 1 + Manifold-SVGExportTest.svg | 1425 ------------------------------------ 2 files changed, 1 insertion(+), 1425 deletions(-) delete mode 100644 Manifold-SVGExportTest.svg diff --git a/.gitignore b/.gitignore index 2a556fdc..b48f839c 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ html /hs_err_*.log /vows.svg.png /flame.svg.png +/Manifold-SVGExportTest.svg diff --git a/Manifold-SVGExportTest.svg b/Manifold-SVGExportTest.svg deleted file mode 100644 index 0793552c..00000000 --- a/Manifold-SVGExportTest.svg +++ /dev/null @@ -1,1425 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From e1e466c3d3b1e9f9d6e9f101f0fa64d786dd8054 Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sat, 11 Apr 2026 17:35:08 -0400 Subject: [PATCH 40/44] latest API --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fdeb351e..46d2442d 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' //manifold 3d - implementation("com.github.madhephaestus:manifold3d:v3.4.1-10") + implementation("com.github.madhephaestus:manifold3d:v3.4.1-9-d40f11b9-SNAPSHOT") } From 9f31aac798c7378951ad7593e2749d9e18c0b2ea Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sat, 11 Apr 2026 17:35:52 -0400 Subject: [PATCH 41/44] cleanup the manifold object on exit of hull --- .../manifold3d/CSGManifold3d.java | 76 +++++++++++++------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java index b6a30744..593c286c 100644 --- a/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java +++ b/src/main/java/com/neuronrobotics/manifold3d/CSGManifold3d.java @@ -109,6 +109,31 @@ public MemorySegment toManifold(CSG csg) throws Throwable { return ms; } + /** + * Returns the index of {@code v} in {@code vertexList}, inserting it if not + * already present. The key is an exact string representation of (x, y, z) using + * {@link Double#toHexString} so that only bit-identical positions are merged, + * matching the BSP's behavior. + */ + private static int intern(Vertex v, Map index, List list) { + // 1. Lower the precision slightly. 1e9 is too high for 'double' stability + // after multiple CSG operations. 1e7 (0.1 nanometer) is the "sweet spot". + double precision = 1e7; + + long x = Math.round(v.pos.x * precision); + long y = Math.round(v.pos.y * precision); + long z = Math.round(v.pos.z * precision); + + String key = x + "," + y + "," + z; + + return index.computeIfAbsent(key, k -> { + int idx = list.size(); + // Use the RAW position for the first instance of this vertex. + // This keeps the geometry as true to the BSP as possible. + list.add(new double[] { v.pos.x, v.pos.y, v.pos.z }); + return idx; + }); + } // ------------------------------------------------------------------------- // helpers @@ -162,22 +187,7 @@ public CSG fromManifold(MemorySegment ms) throws Throwable { // ------------------------------------------------------------------------- // helpers - /** - * Returns the index of {@code v} in {@code vertexList}, inserting it if not - * already present. The key is an exact string representation of (x, y, z) using - * {@link Double#toHexString} so that only bit-identical positions are merged, - * matching the BSP's behavior. - */ - private static int intern(Vertex v, Map index, List list) { - String key = Double.toHexString(v.pos.x) + "," + Double.toHexString(v.pos.y) + "," - + Double.toHexString(v.pos.z); - return index.computeIfAbsent(key, k -> { - int idx = list.size(); - list.add(new double[] { v.pos.x, v.pos.y, v.pos.z }); - return idx; - }); - } private static Vector3d vertexAt(double[] verts, int index) { int base = index * 3; @@ -233,7 +243,7 @@ public ArrayList sliceAtZero(CSG incoming, Transform slicePlane) throws return result; } catch (Throwable e) { - if(csgm!=null) + if (csgm != null) manifold.delete(csgm); throw new RuntimeException("Failed to slice CSG at Z=0", e); } @@ -253,7 +263,9 @@ public CSG union(CSG a, CSG b) throws Throwable { try { MemorySegment result = manifold.union(ma, mb); checkResult(result); - return fromManifold(result); + CSG fromManifold = fromManifold(result); + manifold.delete(result); + return fromManifold; } finally { manifold.delete(ma); manifold.delete(mb); @@ -270,7 +282,9 @@ public CSG difference(CSG a, CSG b) throws Throwable { try { MemorySegment result = manifold.difference(ma, mb); checkResult(result); - return fromManifold(result); + CSG fromManifold = fromManifold(result); + manifold.delete(result); + return fromManifold; } finally { manifold.delete(ma); manifold.delete(mb); @@ -287,7 +301,9 @@ public CSG intersection(CSG a, CSG b) throws Throwable { try { MemorySegment result = manifold.intersection(ma, mb); checkResult(result); - return fromManifold(result); + CSG fromManifold = fromManifold(result); + manifold.delete(result); + return fromManifold; } finally { manifold.delete(ma); manifold.delete(mb); @@ -311,7 +327,9 @@ public CSG hull(CSG a) throws Throwable { try { MemorySegment result = manifold.hull(ma); checkResult(result); - return fromManifold(result); + CSG fromManifold = fromManifold(result); + manifold.delete(result); + return fromManifold; } finally { manifold.delete(ma); } @@ -340,10 +358,17 @@ public CSG hull(CSG... solids) throws Throwable { private void checkResult(MemorySegment... memorySegments) throws Throwable { for (int i = 0; i < memorySegments.length; i++) { MemorySegment ms = memorySegments[i]; - ManifoldError result = manifold.status(ms); + ManifoldError err = manifold.status(ms); //System.out.println("Status of Manifold Op is "+result); - if (result != ManifoldError.NO_ERROR) - throw new NonManifoldShapeError("Error was " + result); + if (err != ManifoldError.NO_ERROR) { + System.out.println("Status: " + err); + System.out.println("Verts: " + manifold.numVert(ms)); + System.out.println("Tris: " + manifold.numTri(ms)); + System.out.println("Genus: " + manifold.genus(ms)); + throw new NonManifoldShapeError("Error was " + err); + }else { + System.out.println("Manifold check ok!"); + } } } @@ -360,7 +385,10 @@ public CSG hull(List points) throws Throwable { mem = manifold.hull(pts); checkResult(mem); - return fromManifold(mem); + CSG fromManifold = fromManifold(mem); + manifold.delete(mem); + mem=null; + return fromManifold; } finally { manifold.delete(mem); } From d81da278f5c33fcd3a054e1b4424261ffccc65de Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sat, 11 Apr 2026 17:36:22 -0400 Subject: [PATCH 42/44] use the Hull operation to skin the sphere --- src/main/java/eu/mihosoft/vrl/v3d/Sphere.java | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java b/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java index 07a4ecab..41187d41 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Sphere.java @@ -34,7 +34,13 @@ package eu.mihosoft.vrl.v3d; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import eu.mihosoft.vrl.v3d.ext.quickhull3d.HullUtil; // Auto-generated Javadoc /** @@ -163,48 +169,48 @@ private void init() { private Vertex sphereVertex(Vector3d c, double r, double theta, double phi) { theta *= Math.PI * 2; phi *= Math.PI; + + // Clamp poles to exact values to avoid sin(PI) floating point error + if (phi <= 0) + return new Vertex(c.plus(new Vector3d(0, r, 0))); + if (phi >= Math.PI) + return new Vertex(c.plus(new Vector3d(0, -r, 0))); + Vector3d dir = new Vector3d(Math.cos(theta) * Math.sin(phi), Math.cos(phi), Math.sin(theta) * Math.sin(phi)); return new Vertex(c.plus(dir.times(r))); } - /* - * (non-Javadoc) - * - * @see eu.mihosoft.vrl.v3d.Primitive#toPolygons() - */ - @Override public List toPolygons() { - if (radius <= 0) - throw new NumberFormatException("radius can not be negative"); - List polygons = new ArrayList<>(); - for (int i = 0; i < numSlices; i++) { - for (int j = 0; j < numStacks; j++) { - - Vertex v0 = sphereVertex(center, radius, i / (double) numSlices, j / (double) numStacks); - Vertex v1 = sphereVertex(center, radius, (i + 1) / (double) numSlices, j / (double) numStacks); - Vertex v2 = sphereVertex(center, radius, (i + 1) / (double) numSlices, (j + 1) / (double) numStacks); - Vertex v3 = sphereVertex(center, radius, i / (double) numSlices, (j + 1) / (double) numStacks); - - if (j == 0) { - addPolygon(polygons, v0, v3, v2); - } else if (j == numStacks - 1) { - addPolygon(polygons, v0, v2, v1); - } else { - addPolygon(polygons, v0, v3, v1); - addPolygon(polygons, v1, v3, v2); - } + List points = new ArrayList<>(); + + // 1. Add the Poles explicitly (avoiding the loop to ensure they are clean) + points.add(new Vector3d(center.x, center.y + radius, center.z)); // North + points.add(new Vector3d(center.x, center.y - radius, center.z)); // South + + // 2. Generate the rings (excluding the pole stacks) + for (int j = 1; j < numStacks; j++) { + double phi = Math.PI * j / numStacks; + double y = Math.cos(phi); + double rRing = Math.sin(phi); + + for (int i = 0; i < numSlices; i++) { + double theta = 2.0 * Math.PI * i / numSlices; + + double x = Math.cos(theta) * rRing; + double z = Math.sin(theta) * rRing; + + // Micro-snapping to zero for stability + if (Math.abs(x) < 1e-12) + x = 0; + if (Math.abs(z) < 1e-12) + z = 0; + + points.add(new Vector3d(center.x + x * radius, center.y + y * radius, center.z + z * radius)); } } - return polygons; + return HullUtil.hull(points, getProperties()).getPolygons(); } - private void addPolygon(List polygons, Vertex... verts) { - try { - polygons.add(new Polygon(List.of(verts), getProperties())); - } catch (ColinearPointsException e) { - e.printStackTrace(); - } - } /** * Gets the center. @@ -262,8 +268,8 @@ public int getNumSlices() { * the numSlices to set */ public Sphere setNumSlices(int numSlices) { - if (numSlices > (NUM_SLICES * 4)) - System.out.println("Very large sphere! this may crash!"); + // if (numSlices > (NUM_SLICES * 4)) + // System.out.println("Very large sphere! this may crash!"); this.numSlices = numSlices; return this; } @@ -284,8 +290,8 @@ public int getNumStacks() { * the numStacks to set */ public Sphere setNumStacks(int numStacks) { - if (numStacks > (NUM_STACKS * 4)) - System.out.println("Very large sphere! this may crash!"); + // if (numStacks > (NUM_STACKS * 4)) + // System.out.println("Very large sphere! this may crash!"); this.numStacks = numStacks; return this; } From caccd850cbeacd3ac699783c8bd5f325bd8e6e8b Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sat, 11 Apr 2026 17:36:48 -0400 Subject: [PATCH 43/44] add the constructor --- src/main/java/eu/mihosoft/vrl/v3d/Vertex.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java b/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java index 5fb7d99c..1fcbe19e 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Vertex.java @@ -81,6 +81,10 @@ private Vertex(Vector3d pos, double weight) { this.weight = weight; } + public Vertex(double x, double y, double z) { + pos = new Vector3d(x, y, z); + } + /* * (non-Javadoc) * From 9572bb0403b168e36596b8018b3054569881be9f Mon Sep 17 00:00:00 2001 From: Kevin Harrington Date: Sat, 11 Apr 2026 17:37:03 -0400 Subject: [PATCH 44/44] save the raw sphere --- src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java index 4f86d4d4..6cce321f 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/Manifold3d_test.java @@ -17,14 +17,15 @@ public void loadTest() throws Throwable { try { CSG.setDefaultOptType(OptType.Manifold3d); CSG cube = new Cube(50,50,50).toCSG(); - CSG sphere = new Sphere(30, 10, 10).toCSG(); + CSG sphere = new Sphere(30,10, 10).toCSG(); + FileUtil.write(Paths.get("Manifole-sphere.stl"), + sphere.toStlString()); List polygons = Slice.slice(sphere, new Transform(), 0); SVGExporter.export(polygons, new File("Manifold-SVGExportTest.svg"), false); CSG difference = cube.difference(sphere); CSG intersect = cube.intersect(sphere); CSG union = cube.union(sphere); - FileUtil.write(Paths.get("Manifole-sphere.stl"), - sphere.toStlString()); + FileUtil.write(Paths.get("Manifole-union.stl"), union.toStlString()); FileUtil.write(Paths.get("Manifole-difference.stl"),