Files
DOSSIER-STANDALONE/src-tauri/dwgimport/src/lib.rs
T
karim 715e950d7c DWG/DXF-Import-Spike: acadrust baut zu wasm32 (Crate dwgimport)
- src-tauri/dwgimport: eigenstaendiges Crate (cdylib+rlib, eigener [workspace]),
  dep acadrust 0.4 (MPL-2.0, pure Rust). parse_dxf_summary(bytes) via
  DxfReader::from_reader + Cursor<Vec<u8>> -> JSON (version/entityCount/
  entityTypeCounts/layerNames). wasm-Fassade parse_dxf_summary_json.
- getrandom wasm_js-Feature noetig (transitiv via ahash) -> als Dep gesetzt.
- Kernbeweis: acadrust kompiliert nach wasm32 (839 KB), DXF-Parse headless
  getestet (5 Entities korrekt). Beweist den DWG/DXF-Import-Weg im Rust/WASM-Kern.
- build:dwgimport-Script, src-tauri/Cargo.toml exclude erweitert.
2026-07-05 03:12:32 +02:00

285 lines
6.0 KiB
Rust

// dwgimport — SPIKE: beweist, dass acadrust (MPL-2.0) aus einem Byte-Buffer
// ein DXF parst und nach wasm32-unknown-unknown kompiliert.
//
// Aufbau:
// - `parse_dxf_summary`: headless, feature-frei testbar.
// - `#[cfg(feature="web")]` wasm-bindgen-Fassade darueber.
//
// API-Stuetzpunkt acadrust:
// `DxfReader::from_reader<R: Read + Seek + 'static>(reader: R)`
// → std::io::Cursor<&[u8]> implementiert Read + Seek → WASM-tauglich.
use std::collections::HashMap;
use std::io::Cursor;
use acadrust::DxfReader;
// serde_json nur bei Feature "web" als optionale Dep, aber serde ist immer da.
// Fuer den headless-Kern bauen wir das JSON mit einem einfachen Format-String,
// damit keine Compile-Time-Dep auf serde_json im Default-Feature noetig ist.
/// Parst ein DXF aus einem Byte-Slice und gibt ein JSON-Summary zurueck.
///
/// Rueckgabe-JSON-Schema:
/// ```json
/// {
/// "version": "AC1015",
/// "entityCount": 5,
/// "entityTypeCounts": { "LINE": 3, "CIRCLE": 2 },
/// "layerNames": ["0", "Wand"]
/// }
/// ```
///
/// Fehler: String mit Fehlerbeschreibung.
pub fn parse_dxf_summary(bytes: &[u8]) -> Result<String, String> {
// Cursor<Vec<u8>> implementiert Read + Seek + 'static — kein std::fs noetig.
// (from_reader verlangt R: 'static; Cursor<&[u8]> wuerde die Lifetime verletzen.)
let cursor = Cursor::new(bytes.to_vec());
let reader = DxfReader::from_reader(cursor)
.map_err(|e| format!("DxfReader::from_reader fehlgeschlagen: {e}"))?;
let doc = reader
.read()
.map_err(|e| format!("DxfReader::read fehlgeschlagen: {e}"))?;
// Versions-String aus dem Enum (Debug-Format, z. B. "AC1015").
let version = format!("{:?}", doc.version);
// Entitaeten zaehlen und nach Typ aufschluesseln.
let mut type_counts: HashMap<&str, usize> = HashMap::new();
for entity in doc.entities() {
let typ = entity.as_entity().entity_type();
*type_counts.entry(typ).or_insert(0) += 1;
}
let entity_count = type_counts.values().sum::<usize>();
// Layer-Namen aus der Layer-Tabelle.
let mut layer_names: Vec<String> = doc
.layers
.iter()
.map(|l| l.name.clone())
.collect();
layer_names.sort();
// JSON-Serialisierung ohne optionale serde_json-Dep im Default-Feature.
// Fuer den headless-Test reicht ein sauberer Format-String.
let type_counts_json = {
let mut pairs: Vec<String> = type_counts
.iter()
.map(|(k, v)| format!(" \"{k}\": {v}"))
.collect();
pairs.sort(); // deterministisch fuer Tests
format!("{{\n{}\n }}", pairs.join(",\n"))
};
let layer_names_json = {
let quoted: Vec<String> = layer_names
.iter()
.map(|n| format!("\"{}\"", n.replace('"', "\\\"")))
.collect();
format!("[{}]", quoted.join(", "))
};
Ok(format!(
"{{\n \"version\": \"{version}\",\n \"entityCount\": {entity_count},\n \"entityTypeCounts\": {type_counts_json},\n \"layerNames\": {layer_names_json}\n}}"
))
}
// ---------------------------------------------------------------------------
// WASM-Fassade (nur bei Feature "web" compiliert)
// ---------------------------------------------------------------------------
#[cfg(feature = "web")]
mod wasm {
use super::parse_dxf_summary;
use wasm_bindgen::prelude::*;
/// Initialisiert den Panic-Hook fuer bessere Fehlermeldungen im Browser.
#[wasm_bindgen(start)]
pub fn init_panic_hook() {
console_error_panic_hook::set_once();
}
/// WASM-Fassade: nimmt einen Byte-Buffer (Uint8Array aus JS) und gibt ein
/// JSON-String zurueck, das das DXF-Summary beschreibt.
///
/// JS-Aufruf:
/// ```js
/// import init, { parse_dxf_summary_json } from './pkgDwgImport/dwgimport.js';
/// await init();
/// const json = parse_dxf_summary_json(new Uint8Array(buffer));
/// ```
#[wasm_bindgen]
pub fn parse_dxf_summary_json(bytes: &[u8]) -> Result<String, JsValue> {
parse_dxf_summary(bytes)
.map_err(|e| JsValue::from_str(&e))
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
/// Minimales ASCII-DXF mit 3 LINE- und 2 CIRCLE-Entitaeten auf Layer "WAND"
/// und Layer "0". Wird direkt als Byte-Slice eingebettet — kein Datei-IO.
const MINI_DXF: &str = r#" 0
SECTION
2
HEADER
9
$ACADVER
1
AC1015
0
ENDSEC
0
SECTION
2
TABLES
0
TABLE
2
LAYER
70
2
0
LAYER
2
0
70
0
62
7
6
Continuous
0
LAYER
2
WAND
70
0
62
3
6
Continuous
0
ENDTAB
0
ENDSEC
0
SECTION
2
ENTITIES
0
LINE
8
WAND
10
0.0
20
0.0
30
0.0
11
100.0
21
0.0
31
0.0
0
LINE
8
WAND
10
100.0
20
0.0
30
0.0
11
100.0
21
50.0
31
0.0
0
LINE
8
0
10
100.0
20
50.0
30
0.0
11
0.0
21
0.0
31
0.0
0
CIRCLE
8
WAND
10
50.0
20
25.0
30
0.0
40
10.0
0
CIRCLE
8
0
10
20.0
20
10.0
30
0.0
40
5.0
0
ENDSEC
0
EOF
"#;
#[test]
fn test_parse_mini_dxf() {
let result = parse_dxf_summary(MINI_DXF.as_bytes());
assert!(result.is_ok(), "parse_dxf_summary schlug fehl: {:?}", result);
let json = result.unwrap();
println!("DXF-Summary:\n{json}");
// Version muss AC1015 sein.
assert!(json.contains("AC1015"), "Version AC1015 nicht im JSON: {json}");
// Genau 5 Entitaeten: 3 LINE + 2 CIRCLE.
assert!(
json.contains("\"entityCount\": 5"),
"entityCount soll 5 sein: {json}"
);
assert!(
json.contains("\"LINE\": 3"),
"LINE-Zaehler soll 3 sein: {json}"
);
assert!(
json.contains("\"CIRCLE\": 2"),
"CIRCLE-Zaehler soll 2 sein: {json}"
);
// Layer "WAND" und "0" muessen erscheinen.
assert!(json.contains("\"WAND\""), "Layer WAND fehlt: {json}");
assert!(json.contains("\"0\""), "Layer 0 fehlt: {json}");
}
}