diff --git a/android/app/build.gradle b/android/app/build.gradle
index 89ce4d9..b330f95 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
applicationId 'org.crabcore.u1control'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 17
- versionName "1.2.7"
+ versionCode 16
+ versionName "1.2.6"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}
diff --git a/android/app/src/main/java/org/crabcore/u1control/slicing/HelixModelPreviewActivity.kt b/android/app/src/main/java/org/crabcore/u1control/slicing/HelixModelPreviewActivity.kt
index 4132f34..9f2d3ec 100644
--- a/android/app/src/main/java/org/crabcore/u1control/slicing/HelixModelPreviewActivity.kt
+++ b/android/app/src/main/java/org/crabcore/u1control/slicing/HelixModelPreviewActivity.kt
@@ -415,8 +415,7 @@ class HelixModelPreviewActivity : Activity() {
private fun loadNativeScene(file: File) {
synchronized(NativeEngineGuard.LOCK) {
val lib = native ?: NativeLibrary().also { native = it }
- val loaded = lib.loadModel(file.absolutePath)
- if (!loaded) {
+ if (!lib.loadModel(file.absolutePath)) {
throw IllegalStateException("Native engine could not load this model.")
}
filamentPalette = readFilamentPalette(lib)
diff --git a/android/app/src/main/java/org/crabcore/u1control/slicing/HelixSlicerModule.kt b/android/app/src/main/java/org/crabcore/u1control/slicing/HelixSlicerModule.kt
index f6d1f67..db0a498 100644
--- a/android/app/src/main/java/org/crabcore/u1control/slicing/HelixSlicerModule.kt
+++ b/android/app/src/main/java/org/crabcore/u1control/slicing/HelixSlicerModule.kt
@@ -333,13 +333,11 @@ class HelixSlicerModule(
return
}
val safeName = plate.name
- .replace(Regex("""[/\\:*?"<>|\s]"""), "_")
+ .replace(Regex("""[/\\:*?"<>|]"""), "_")
.trim()
.ifBlank { "plate_$plateId" }
val outFile = File(reactApplicationContext.filesDir, "plate_${plateId}_$safeName.3mf")
- PlateExtractor.extractPlate(file, plate, outFile) { percent, phase ->
- emitExtractProgress(percent, phase)
- }
+ PlateExtractor.extractPlate(file, plate, outFile)
if (!outFile.exists() || outFile.length() == 0L) {
promise.reject("E_EXTRACT", "Failed to extract plate $plateId.")
return
@@ -354,16 +352,6 @@ class HelixSlicerModule(
}
}
- private fun emitExtractProgress(percent: Int, phase: String) {
- val params = Arguments.createMap().apply {
- putInt("percent", percent)
- putString("phase", phase)
- }
- reactApplicationContext
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
- .emit("extractProgress", params)
- }
-
@ReactMethod
fun openMakerWorldDownloader(
designId: String,
diff --git a/android/app/src/main/java/org/crabcore/u1control/slicing/PlateExtractor.kt b/android/app/src/main/java/org/crabcore/u1control/slicing/PlateExtractor.kt
index 72ca815..ecad74d 100644
--- a/android/app/src/main/java/org/crabcore/u1control/slicing/PlateExtractor.kt
+++ b/android/app/src/main/java/org/crabcore/u1control/slicing/PlateExtractor.kt
@@ -1,14 +1,9 @@
package org.crabcore.u1control.slicing
import java.io.File
-import java.io.InputStream
-import java.io.OutputStreamWriter
-import java.io.Writer
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserFactory
/**
* Reads and splits Bambu/OrcaSlicer multi-plate 3MFs.
@@ -55,8 +50,6 @@ object PlateExtractor {
Regex("]*>.*?", setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE))
private val ASSEMBLE_ITEM = Regex("]*?/>", RegexOption.IGNORE_CASE)
private val ASSEMBLE_ITEM_OBJECT_ID = Regex("\\bobject_id=\"(\\d+)\"", RegexOption.IGNORE_CASE)
- private val COMPONENT_PATH = Regex("]*\\bp:path=\"([^\"]+)\"", RegexOption.IGNORE_CASE)
- private val RELATIONSHIP = Regex("]*\\bTarget=\"([^\"]+)\"[^>]*/?>", RegexOption.IGNORE_CASE)
/** Plate definitions in file order. Empty when the 3MF isn't multi-plate. */
fun readPlates(file: File): List {
@@ -87,368 +80,34 @@ object PlateExtractor {
}
/**
- * Repacks a single plate of [src] into [outFile] as a MINIMAL core-spec 3MF.
- *
- * PrusaSlicer's loadModel() is strict: it rejects production-extension files
- * (Bambu/Orca) that carry the BambuStudio namespace, Metadata plate_N.json,
- * Auxiliaries blobs, etc. So we emit only the three parts a core 3MF needs
- * ([Content_Types].xml, _rels/.rels, 3D/3dmodel.model), with the build
- * flattened to this plate's mesh objects.
- *
- * STREAMING: large 3MFs hold hundreds of MB of mesh data — a single
- * 3D/3dmodel.model can be ~400 MB (Bambu also splits geometry across 50 MB+
- * part files). Materializing any of that as a Java String blows the heap
- * (a char[] is ~2x the byte size, and the heap is capped at 256 MB). So we
- * never do: the object graph is built with a streaming XmlPullParser that
- * records only structure (component children, transforms, mesh presence) —
- * never vertex data — and each kept mesh is stream-copied element-by-element
- * from the source zip entry straight into the output writer. Peak memory is
- * bounded by the parser buffer, not the model size.
- *
- * Two mesh-storage shapes are supported transparently:
- * - INLINE (OrcaSlicer / some Bambu): the leaf object carries its
- * inside 3D/3dmodel.model's .
- * - EXTERNAL part files (Bambu production extension): the mesh lives only in
- * a sibling 3D/Objects/object_N.model, referenced by a mesh-less group
- * object via .
+ * Writes a single-plate copy of [src] to [outFile]: every zip entry is copied
+ * verbatim except 3D/3dmodel.model, whose is filtered to the plate's
+ * objects. Returns [outFile].
*/
- fun extractPlate(
- src: File,
- plate: Plate,
- outFile: File,
- onProgress: ((percent: Int, phase: String) -> Unit)? = null,
- ): File {
+ fun extractPlate(src: File, plate: Plate, outFile: File): File {
val keep = plate.objectIds.toHashSet()
- onProgress?.invoke(2, "Opening archive…")
ZipFile(src).use { zip ->
- val modelEntry = zip.getEntry("3D/3dmodel.model")
- ?: throw IllegalStateException("3D/3dmodel.model not found in ${src.name}")
- val modelSize = modelEntry.size.coerceAtLeast(0L)
-
- // PASS 1 — stream the object graph + build items (no mesh data buffered).
- val graph = HashMap()
- val items = ArrayList()
- val rootUnit = arrayOf("millimeter", "") // [0]=unit, [1]=xml:lang
- val m1 = Meter(modelSize, 3, 27, onProgress).apply { phase = "Reading model structure…" }
- streamGraph(zip, modelEntry, graph, items, rootUnit, m1)
- if (items.isEmpty()) throw IllegalStateException("No items in ${src.name}")
- onProgress?.invoke(31, "Resolving objects…")
-
- // Resolve the plate's leaves: DFS from kept build items, composing 3MF
- // row-vector transforms (combined = component x item). One output object
- // per UNIQUE source mesh (shared geometry -> one