cadmpeg-asm 0.6.1

Parse and write Autodesk Shape Manager record streams.
Documentation

cadmpeg-asm

cadmpeg-asm parses Autodesk Shape Manager and admitted Spatial ACIS binary model streams. It reads ASM BinaryFile4/BinaryFile8 and ACIS 217/218 headers, frames SAB token streams, locates the boundary between solved records and construction history, and reports byte locations for payload fields.

Install

cargo add cadmpeg-asm

Parse kernel headers

asm_header::has_asm_magic recognizes the 15-byte ASM BinaryFile4 and ASM BinaryFile8 prefixes. asm_header::parse returns a BinaryHeader with a required RefWidth and KernelHeader metadata. The metadata stores save_format_version, entity_count, flags, product strings, and tolerance fields that the input provides. An unreadable width returns None; recognized but incomplete metadata retains unavailable fields as None.

acis_header::has_acis_magic recognizes ACIS BinaryFile. acis_header::parse admits its 32-bit header and returns a BinaryHeader with RefWidth::Four and the same KernelHeader metadata. Every ACIS save format uses this parser. Majors 217 and 218 are the bands the record decoders are verified against, which dialect states: a stream outside them is framed and decoded the same way, and the host labels the result Admission::Unverified and charges its source.dialect-unverified loss.

BinaryFile4 stores the save-format version, record count, entity count, and flags as little-endian u32 words at offsets 15, 19, 23, and 27. Its string region begins at byte 31. BinaryFile8 stores the save-format version at byte 15, the entity count as a little-endian u64 at byte 31, and flags as a little-endian u64 at byte 39. Its string region begins at byte 47. Both layouts carry three 0x07-tagged strings followed by three 0x06-tagged little-endian f64 values. The values populate product_family, product_version, save_date, scale, linear (resabs), and angular (resnor).

KernelHeader::save_format_major and save_format_minor split the encoded save-format version, where 100 * major + minor is the stored value. KernelHeader::has_history_partition reads HISTORY_PARTITION_FLAG from flags. FORMAT_REVISION_FLAGS identifies bits 1 through 7. The flags field retains the complete word.

asm_header::record_stream_start returns the byte after the fixed words, the three strings, and the three tolerance values. asm_header::solved_record_limit attempts to locate the history partition's first record when the header declares HISTORY_PARTITION_FLAG. It recognizes the Begin-of-ASM-History-Data preamble and the earlier delta_state boundary. asm_header::parse returns the declared integer/reference RefWidth in BinaryHeader::width. RefWidth::bytes() returns the encoded payload size.

Frame SAB records

sab::frame(bytes, start, limit, ref_width) returns a Vec<Record> for the requested byte range. ref_width is RefWidth::Four or RefWidth::Eight. A 0x11 tag terminates a record at subtype depth zero. frame stops at the supplied limit or at the delta_state history boundary. An inverted range, a limit beyond the available bytes, or an incomplete record at the limit fails. sab::frame_history accepts a final history record that ends at the supplied limit without a 0x11 terminator.

An unknown tag or truncated payload returns StreamError, which stores the stream format, byte offset, and reason string. Framing preserves recognized payload tokens, record names, and record extents. A Record contains the zero-based index, hyphen-joined name, retained tokens in an Arc<[Token]>, starting offset, and byte len including the record terminator when present. Record::head() returns the leading name component.

Record names use 0x0e sub-identifiers followed by a 0x0d identifier. Those tags become Token::SubIdent and Token::Ident when they occur in a payload. Token::is_payload_ident identifies those payload identifiers. Record::chunks, Record::chunk, and Record::chunk_len index value tokens and skip them. Record::ref_at returns a non-null Token::Ref value at a chunk index; the -1 null reference produces None.

The framer maps SAB tags to Token variants as follows:

Tags Variants
0x02, 0x03, 0x04, 0x05, 0x06 Char(u8), Short(i16), Long(i64), Float(f32), Double(f64)
0x07, 0x08, 0x09, 0x12 Str(String) with one-byte, two-byte, or ref_width length prefixes
0x0a, 0x0b, 0x0c True, False, Ref(i64)
0x0f, 0x10 SubtypeOpen, SubtypeClose
0x13, 0x14, 0x16 Position([f64; 3]), Vector3([f64; 3]), Vector2([f64; 2])
0x15, 0x17 Enum(i64), Int64(i64)
payload 0x0d, 0x0e Ident(String), SubIdent(String)

Long, Ref, and Enum use ref_width. Int64 always uses eight bytes. The 0x11 record terminator is a framing control tag and has no Token variant.

Locate payload bytes

sab::payload_token returns a decoded value token and its absolute byte offset at a record's chunk index, using the same value-token indexing as Record::chunk. With the test-support feature, test_support::sab::payload_token_offsets locates all offsets for a selected payload tag in a test fixture and reports a StreamError when the record cannot be lexed.

sab::payload_subtype_range returns the absolute byte range inside the subtype at a chunk index when its following identifier matches the requested name. The range starts after that identifier and ends before the matching Token::SubtypeClose; nested subtype scopes remain in the range.

Frame a synthetic SAB record:

use cadmpeg_asm::kernel_header::RefWidth;
use cadmpeg_asm::sab::{frame, Token};

fn main() {
    let mut bytes = vec![0x0d, 4];
    bytes.extend_from_slice(b"body");
    bytes.push(0x0c);
    bytes.extend_from_slice(&1_i64.to_le_bytes());
    bytes.push(0x11);

    let records = frame(&bytes, 0, bytes.len(), RefWidth::Eight).expect("valid SAB record");
    let record = &records[0];

    assert_eq!(record.name, "body");
    assert!(matches!(record.chunk(0), Some(Token::Ref(1))));
    assert_eq!(record.ref_at(0), Some(1));
}

Documentation

Requires Rust 1.88 or later. Licensed under Apache-2.0.