715e950d7c
- 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.
285 lines
6.0 KiB
Rust
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}");
|
|
}
|
|
}
|