use crate::{Result, RevitFile};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
pub const PARTITION_LEVEL_SUPPORTED_REVIT_VERSIONS: &[u32] = &[2023, 2024, 2025];
pub const REVIT_2023: u32 = 2023;
pub const OST_LEVELS: i64 = -2_000_240;
pub const RECORD_MARKER_OFFSET: usize = 0x50;
pub const RECORD_MARKER: [u8; 6] = [0xff, 0xff, 0xff, 0xff, 0xab, 0x05];
pub const RECORD_MIN_LEN: usize = RECORD_MARKER_OFFSET + RECORD_MARKER.len() + 2;
pub const OWNER_OFFSET_BEFORE_NAME: usize = 0x47;
pub const OWNER_SENTINEL_RUN_LEN: usize = 56;
pub const LENGTH_PREFIX_OFFSET_BEFORE_NAME: usize = 4;
pub const MAX_NAME_CHARS: usize = 128;
pub const ELEVATION_MARKER: [u8; 8] = [0x05, 0x00, 0x00, 0x00, 0x48, 0x02, 0x00, 0x00];
pub fn elevation_marker(revit_version: u32) -> Option<[u8; 6]> {
match revit_version {
2024 => Some([0x05, 0x00, 0x00, 0x00, 0x48, 0x02]),
2025 => Some([0x05, 0x00, 0x00, 0x00, 0x5d, 0x02]),
_ => None,
}
}
pub fn schema_elevation_marker(rf: &mut RevitFile) -> Option<[u8; 6]> {
let classes = rf.schema_classes().ok()?;
let plane = classes.classes.iter().find(|c| c.name == "Plane")?.tag;
let mut marker = [0x05, 0x00, 0x00, 0x00, 0x00, 0x00];
marker[4..].copy_from_slice(&plane.to_le_bytes());
Some(marker)
}
pub fn record_marker(revit_version: u32) -> Option<[u8; 6]> {
let marker = crate::partition_element_records::bbox_marker(revit_version)?;
let mut out = [0u8; 6];
out.copy_from_slice(&marker[2..]);
Some(out)
}
pub const ELEVATION_CONFIRM_LEAD_IN: usize = 24;
pub const ELEVATION_OFFSET_AFTER_MARKER: usize = 55;
pub const ELEVATION_CONFIRM_STRIDE: usize = 153;
pub const ELEVATION_MARKER_SEARCH_BYTES: usize = 2048;
pub const MAX_ELEVATION_FEET: f64 = 1.0e7;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PartitionLevelRecord {
pub stream: String,
pub offset: usize,
pub element_id: u32,
pub flags: u32,
pub container: u64,
pub placement_kind: u32,
}
impl PartitionLevelRecord {
pub fn is_container_member(&self) -> bool {
self.container != crate::partition_element_records::CONTAINER_NONE
}
pub fn is_placed_instance(&self) -> bool {
self.placement_kind == crate::partition_element_records::PLACEMENT_KIND_INSTANCE
}
pub fn is_level_element(&self) -> bool {
!self.is_container_member() && self.is_placed_instance()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PartitionLevel {
pub element_id: u32,
pub name: String,
pub elevation_feet: f64,
#[serde(default)]
pub building_story: Option<bool>,
}
pub fn supports_revit_version(revit_version: u32) -> bool {
PARTITION_LEVEL_SUPPORTED_REVIT_VERSIONS.contains(&revit_version)
}
fn read_u16(buf: &[u8], off: usize) -> Option<u16> {
buf.get(off..off + 2)
.map(|s| u16::from_le_bytes(s.try_into().expect("2 bytes")))
}
fn read_u32(buf: &[u8], off: usize) -> Option<u32> {
buf.get(off..off + 4)
.map(|s| u32::from_le_bytes(s.try_into().expect("4 bytes")))
}
fn read_u64(buf: &[u8], off: usize) -> Option<u64> {
buf.get(off..off + 8)
.map(|s| u64::from_le_bytes(s.try_into().expect("8 bytes")))
}
fn read_f64(buf: &[u8], off: usize) -> Option<f64> {
read_u64(buf, off).map(f64::from_bits)
}
pub fn decode_record_at(
stream: &str,
buf: &[u8],
offset: usize,
declared_ids: &BTreeSet<u32>,
) -> Option<PartitionLevelRecord> {
decode_record_at_with(stream, buf, offset, declared_ids, &RECORD_MARKER, &[])
}
pub fn decode_record_at_with(
stream: &str,
buf: &[u8],
offset: usize,
declared_ids: &BTreeSet<u32>,
marker: &[u8; 6],
chain: &[crate::partition_element_records::PartitionRecordSpan],
) -> Option<PartitionLevelRecord> {
use crate::partition_element_records as per;
if offset.checked_add(RECORD_MIN_LEN)? > buf.len() {
return None;
}
let raw_id = read_u64(buf, offset)?;
let element_id = if per::carries_no_element_id(raw_id) {
u32::try_from(per::enclosing_record(chain, offset)?.element_id).ok()?
} else {
raw_id as u32
};
if element_id == 0 || !declared_ids.contains(&element_id) {
return None;
}
if read_u16(buf, offset + 0x10)? != 0 {
return None;
}
if read_u64(buf, offset + per::CATEGORY_OFFSET)? as i64 != OST_LEVELS {
return None;
}
if buf[offset + RECORD_MARKER_OFFSET..offset + RECORD_MARKER_OFFSET + marker.len()] != *marker {
return None;
}
let flags = read_u32(buf, offset + 0x08)?;
let container = read_u64(buf, offset + per::CONTAINER_OFFSET)?;
let placement_kind = read_u32(buf, offset + per::PLACEMENT_KIND_OFFSET)?;
Some(PartitionLevelRecord {
stream: stream.to_string(),
offset,
element_id,
flags,
container,
placement_kind,
})
}
pub fn find_level_records(
stream: &str,
buf: &[u8],
declared_ids: &BTreeSet<u32>,
) -> Vec<PartitionLevelRecord> {
find_level_records_with(stream, buf, declared_ids, &RECORD_MARKER, &[])
}
pub fn find_level_records_with(
stream: &str,
buf: &[u8],
declared_ids: &BTreeSet<u32>,
marker: &[u8; 6],
chain: &[crate::partition_element_records::PartitionRecordSpan],
) -> Vec<PartitionLevelRecord> {
use crate::partition_element_records as per;
let needle = (OST_LEVELS as u64).to_le_bytes();
let mut out = Vec::new();
let mut cursor = 0usize;
while cursor + needle.len() <= buf.len() {
let Some(found) = find_subslice(&buf[cursor..], &needle) else {
break;
};
let hit = cursor + found;
if hit >= per::CATEGORY_OFFSET {
if let Some(record) = decode_record_at_with(
stream,
buf,
hit - per::CATEGORY_OFFSET,
declared_ids,
marker,
chain,
) {
out.push(record);
}
}
cursor = hit + 1;
}
out
}
#[derive(Debug, Clone, PartialEq)]
pub struct NameElevationBlock {
pub element_id: u32,
pub name: String,
pub elevation_feet: f64,
pub building_story: Option<bool>,
}
pub const BUILDING_STORY_ANCHOR: [u8; 10] =
[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01];
pub const BUILDING_STORY_WINDOW: usize = 0x200;
fn building_story_after(buf: &[u8], name_end: usize) -> Option<bool> {
let window = buf.get(name_end..(name_end + BUILDING_STORY_WINDOW).min(buf.len()))?;
let anchor = name_end + find_subslice(window, &BUILDING_STORY_ANCHOR)?;
let id_at = anchor + BUILDING_STORY_ANCHOR.len() + 8;
if read_u64(buf, id_at)? > u64::from(u32::MAX) {
return None;
}
match buf.get(id_at + 8)? {
0 => Some(false),
1 => Some(true),
_ => None,
}
}
pub fn decode_name_block_at(
buf: &[u8],
run_start: usize,
declared_ids: &BTreeSet<u32>,
) -> Option<NameElevationBlock> {
decode_name_block_at_with(buf, run_start, declared_ids, &ELEVATION_MARKER[..6])
}
pub fn decode_name_block_at_with(
buf: &[u8],
run_start: usize,
declared_ids: &BTreeSet<u32>,
marker: &[u8],
) -> Option<NameElevationBlock> {
decode_name_block(
buf,
run_start,
declared_ids,
marker,
8,
OWNER_SENTINEL_RUN_LEN,
)
}
pub const OWNER_SENTINEL_RUN_LEN_2023: usize = 28;
fn decode_name_block(
buf: &[u8],
run_start: usize,
declared_ids: &BTreeSet<u32>,
marker: &[u8],
id_len: usize,
run_len: usize,
) -> Option<NameElevationBlock> {
let value = run_start.checked_add(run_len + 3 + LENGTH_PREFIX_OFFSET_BEFORE_NAME)?;
let owner_at = run_start.checked_sub(id_len)?;
let run = buf.get(run_start..run_start + run_len)?;
if !run.iter().all(|byte| *byte == 0xff) {
return None;
}
let pad = buf.get(run_start + run_len..value - LENGTH_PREFIX_OFFSET_BEFORE_NAME)?;
if !matches!(pad.first(), Some(0 | 1)) || !pad[1..].iter().all(|byte| *byte == 0) {
return None;
}
let owner = if id_len == 4 {
u64::from(read_u32(buf, owner_at)?)
} else {
read_u64(buf, owner_at)?
};
if owner == 0 || owner > u64::from(u32::MAX) {
return None;
}
let element_id = owner as u32;
if !declared_ids.contains(&element_id) {
return None;
}
let chars = read_u32(buf, value - LENGTH_PREFIX_OFFSET_BEFORE_NAME)? as usize;
if chars == 0 || chars > MAX_NAME_CHARS {
return None;
}
let end = value.checked_add(chars.checked_mul(2)?)?;
let raw = buf.get(value..end)?;
let units: Vec<u16> = raw
.chunks_exact(2)
.map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
.collect();
let name = String::from_utf16(&units).ok()?;
if name.chars().any(|c| c.is_control()) {
return None;
}
let window = buf.get(end..(end + ELEVATION_MARKER_SEARCH_BYTES).min(buf.len()))?;
let at = end + find_subslice(window, marker)? + ELEVATION_OFFSET_AFTER_MARKER;
let elevation_feet = read_f64(buf, at)?;
if !elevation_feet.is_finite() || elevation_feet.abs() > MAX_ELEVATION_FEET {
return None;
}
let copy = buf.get(at.checked_sub(ELEVATION_CONFIRM_LEAD_IN)?..at + 8)?;
let after = buf.get(at + 8..(at + 8 + ELEVATION_MARKER_SEARCH_BYTES).min(buf.len()))?;
find_subslice(after, copy)?;
Some(NameElevationBlock {
element_id,
name,
elevation_feet,
building_story: building_story_after(buf, end),
})
}
pub fn find_name_blocks(buf: &[u8], declared_ids: &BTreeSet<u32>) -> Vec<NameElevationBlock> {
find_name_blocks_with(buf, declared_ids, &ELEVATION_MARKER[..6])
}
pub fn find_name_blocks_with(
buf: &[u8],
declared_ids: &BTreeSet<u32>,
marker: &[u8],
) -> Vec<NameElevationBlock> {
find_name_blocks_sized(buf, declared_ids, marker, 8, OWNER_SENTINEL_RUN_LEN)
}
fn find_name_blocks_sized(
buf: &[u8],
declared_ids: &BTreeSet<u32>,
marker: &[u8],
id_len: usize,
run_len: usize,
) -> Vec<NameElevationBlock> {
let mut out = Vec::new();
let run = vec![0xffu8; run_len];
let finder = memchr::memmem::Finder::new(&run);
let mut index = 0usize;
while let Some(found) = finder.find(&buf[index..]) {
let mut run_start = index + found;
while run_start > index && buf[run_start - 1] == 0xff {
run_start -= 1;
}
index = run_start;
while index < buf.len() && buf[index] == 0xff {
index += 1;
}
if let Some(block) =
decode_name_block(buf, run_start, declared_ids, marker, id_len, run_len)
{
out.push(block);
}
}
out
}
fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() || haystack.len() < needle.len() {
return None;
}
memchr::memmem::find(haystack, needle)
}
pub fn owned_level_ids(buf: &[u8], level_ids: &BTreeSet<u32>) -> BTreeSet<u32> {
let mut out = BTreeSet::new();
for (id, owner_at) in crate::partition_id_objects::find_id_objects(buf, level_ids) {
let value = owner_at + OWNER_OFFSET_BEFORE_NAME;
let Some(chars) = read_u32(buf, value - LENGTH_PREFIX_OFFSET_BEFORE_NAME) else {
continue;
};
if !(1..=MAX_NAME_CHARS as u32).contains(&chars) {
continue;
}
let sentinel = buf.get(owner_at + 8..owner_at + 16);
let owner = read_u64(buf, owner_at + 16);
if sentinel.is_some_and(|run| run.iter().all(|b| *b == 0xff))
&& owner.is_some_and(|o| o != 0 && o < u64::from(u32::MAX))
{
out.insert(id);
}
}
out
}
pub fn levels_from_records_and_blocks(
records: &[PartitionLevelRecord],
blocks: impl IntoIterator<Item = NameElevationBlock>,
) -> Vec<PartitionLevel> {
let level_ids: BTreeSet<u32> = records
.iter()
.filter(|record| record.is_level_element())
.map(|record| record.element_id)
.collect();
let mut accepted: BTreeMap<u32, PartitionLevel> = BTreeMap::new();
let mut conflicting: BTreeSet<u32> = BTreeSet::new();
for block in blocks {
if !level_ids.contains(&block.element_id) {
continue;
}
let level = PartitionLevel {
element_id: block.element_id,
name: block.name,
elevation_feet: block.elevation_feet,
building_story: block.building_story,
};
match accepted.get(&level.element_id) {
Some(existing)
if existing.name != level.name
|| existing.elevation_feet.to_bits() != level.elevation_feet.to_bits() =>
{
conflicting.insert(level.element_id);
}
Some(_) => {}
None => {
accepted.insert(level.element_id, level);
}
}
}
for id in conflicting {
accepted.remove(&id);
}
let mut out: Vec<PartitionLevel> = accepted.into_values().collect();
out.sort_by(|a, b| {
a.elevation_feet
.partial_cmp(&b.elevation_feet)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| a.element_id.cmp(&b.element_id))
});
out
}
pub fn recovered_levels_are_a_storey_set(
records: &[PartitionLevelRecord],
levels: &[PartitionLevel],
) -> bool {
let level_records = records
.iter()
.filter(|record| record.is_level_element())
.map(|record| record.element_id)
.collect::<BTreeSet<u32>>()
.len();
if levels.len() < 2 || levels.len() != level_records {
return false;
}
let mut seen: BTreeSet<u64> = BTreeSet::new();
levels
.iter()
.all(|level| seen.insert(level.elevation_feet.to_bits()))
}
pub fn scan_partition_level_ids(
rf: &mut RevitFile,
revit_version: u32,
declared_ids: &BTreeSet<u32>,
) -> Result<BTreeSet<u32>> {
if !supports_revit_version(revit_version) || declared_ids.is_empty() {
return Ok(BTreeSet::new());
}
Ok(level_records(rf, revit_version, declared_ids)
.into_iter()
.filter(PartitionLevelRecord::is_level_element)
.map(|record| record.element_id)
.collect())
}
const ID_BEFORE_CATEGORY_2023: usize = 14;
const HEADER_TAG_AFTER_ID_2023: usize = 8;
const CONTAINER_AFTER_CATEGORY_2023: usize = 8;
const PLACEMENT_KIND_AFTER_CATEGORY_2023: usize = 24;
const MARKER_AFTER_CATEGORY_2023: usize = 38;
pub fn find_level_records_2023(
stream: &str,
buf: &[u8],
declared_ids: &BTreeSet<u32>,
header_tag: u16,
marker: &[u8; 6],
) -> Vec<PartitionLevelRecord> {
let needle = (OST_LEVELS as u64).to_le_bytes();
memchr::memmem::find_iter(buf, &needle)
.filter_map(|category| {
let id_at = category.checked_sub(ID_BEFORE_CATEGORY_2023)?;
let element_id = read_u32(buf, id_at)?;
if element_id == 0 || !declared_ids.contains(&element_id) {
return None;
}
if read_u16(buf, id_at + HEADER_TAG_AFTER_ID_2023)? != header_tag {
return None;
}
let marker_at = category + MARKER_AFTER_CATEGORY_2023;
if buf.get(marker_at..marker_at + marker.len())? != marker {
return None;
}
Some(PartitionLevelRecord {
stream: stream.to_string(),
offset: id_at,
element_id,
flags: read_u32(buf, id_at + 4)?,
container: read_u64(buf, category + CONTAINER_AFTER_CATEGORY_2023)?,
placement_kind: read_u32(buf, category + PLACEMENT_KIND_AFTER_CATEGORY_2023)?,
})
})
.collect()
}
fn level_records(
rf: &mut RevitFile,
revit_version: u32,
declared_ids: &BTreeSet<u32>,
) -> Vec<PartitionLevelRecord> {
if revit_version == REVIT_2023 {
let Some((full, header_tag)) = crate::partition_element_records_2023::record_marker(rf)
else {
return Vec::new();
};
let mut marker = [0u8; 6];
marker.copy_from_slice(&full[2..]);
let mut records = Vec::new();
for stream in rf.partition_stream_names() {
let Ok(inflated) = rf.inflated_partition(&stream) else {
continue;
};
records.extend(find_level_records_2023(
&stream,
inflated.bytes(),
declared_ids,
header_tag,
&marker,
));
}
return records;
}
let (Some(marker), Some(bbox_marker)) = (
record_marker(revit_version),
crate::partition_element_records::bbox_marker(revit_version),
) else {
return Vec::new();
};
let mut records = Vec::new();
for stream in rf.partition_stream_names() {
let Ok(inflated) = rf.inflated_partition(&stream) else {
continue;
};
let buf = inflated.bytes();
let chain = crate::partition_element_records::partition_record_chain(buf, &bbox_marker);
records.extend(find_level_records_with(
&stream,
buf,
declared_ids,
&marker,
&chain,
));
}
records
}
pub fn scan_partition_levels(
rf: &mut RevitFile,
revit_version: u32,
declared_ids: &BTreeSet<u32>,
) -> Result<Vec<PartitionLevel>> {
if !supports_revit_version(revit_version) || declared_ids.is_empty() {
return Ok(Vec::new());
}
let Some(marker) = elevation_marker(revit_version).or_else(|| schema_elevation_marker(rf))
else {
return Ok(Vec::new());
};
let records = level_records(rf, revit_version, declared_ids);
let level_ids: BTreeSet<u32> = records
.iter()
.filter(|record| record.is_level_element())
.map(|record| record.element_id)
.collect();
let mut numbered: Vec<(u32, NameElevationBlock)> = Vec::new();
let mut owned = BTreeSet::new();
for stream in rf.partition_stream_names() {
let Ok(inflated) = rf.inflated_partition(&stream) else {
continue;
};
let number = stream
.rsplit('/')
.next()
.and_then(|n| n.parse::<u32>().ok())
.unwrap_or(0);
let mut blocks = Vec::new();
let buf = inflated.bytes();
if revit_version == REVIT_2023 {
blocks.extend(find_name_blocks_sized(
buf,
declared_ids,
&marker,
4,
OWNER_SENTINEL_RUN_LEN_2023,
));
} else {
blocks.extend(find_name_blocks_with(buf, declared_ids, &marker));
owned.extend(owned_level_ids(buf, &level_ids));
}
numbered.extend(blocks.into_iter().map(|block| (number, block)));
}
let mut newest: BTreeMap<u32, u32> = BTreeMap::new();
for (number, block) in &numbered {
let held = newest.entry(block.element_id).or_insert(*number);
*held = (*held).max(*number);
}
let blocks: Vec<NameElevationBlock> = numbered
.into_iter()
.filter(|(number, block)| newest.get(&block.element_id) == Some(number))
.map(|(_, block)| block)
.collect();
let records: Vec<PartitionLevelRecord> = records
.into_iter()
.filter(|record| !owned.contains(&record.element_id))
.collect();
let levels = levels_from_records_and_blocks(&records, blocks);
if !recovered_levels_are_a_storey_set(&records, &levels) {
return Ok(Vec::new());
}
Ok(levels)
}
pub fn scan_level_objects(
rf: &mut RevitFile,
declared: &BTreeSet<u32>,
level_ids: &BTreeSet<u32>,
) -> BTreeMap<u32, u32> {
let mut found: BTreeMap<u32, Option<u32>> = BTreeMap::new();
for stream in rf.partition_stream_names() {
let Ok(inflated) = rf.inflated_partition(&stream) else {
continue;
};
let buf = inflated.bytes();
for at in memchr::memmem::find_iter(buf, &[1u8, 0, 0, 0]) {
let (Some(id), Some(level)) = (read_u64(buf, at + 4), read_u64(buf, at + 12)) else {
continue;
};
let (Ok(id), Ok(level)) = (u32::try_from(id), u32::try_from(level)) else {
continue;
};
if !declared.contains(&id) || level_ids.contains(&id) || !level_ids.contains(&level) {
continue;
}
let held = found.entry(id).or_insert(Some(level));
if *held != Some(level) {
*held = None;
}
}
}
found
.into_iter()
.filter_map(|(id, level)| level.map(|level| (id, level)))
.collect()
}
pub fn recover_partition_levels(
rf: &mut RevitFile,
revit_version: u32,
) -> Result<Vec<PartitionLevel>> {
if !supports_revit_version(revit_version) {
return Ok(Vec::new());
}
let declared: BTreeSet<u32> = match crate::elem_table::parse_records(rf) {
Ok(records) => crate::elem_table::declared_ids(&records),
Err(_) => return Ok(Vec::new()),
};
scan_partition_levels(rf, revit_version, &declared)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::partition_element_records::{
CONTAINER_NONE, PLACEMENT_KIND_INSTANCE, PLACEMENT_KIND_SYMBOL,
};
fn declared(ids: &[u32]) -> BTreeSet<u32> {
ids.iter().copied().collect()
}
fn synth_record(element_id: u32, container: u64, placement_kind: u32) -> Vec<u8> {
let mut buf = vec![0xffu8; RECORD_MIN_LEN];
buf[0..8].copy_from_slice(&u64::from(element_id).to_le_bytes());
buf[8..12].copy_from_slice(&0x87u32.to_le_bytes());
buf[12..16].copy_from_slice(&0x059fu32.to_le_bytes());
buf[16..18].copy_from_slice(&0u16.to_le_bytes());
buf[0x12..0x1a].copy_from_slice(&(OST_LEVELS as u64).to_le_bytes());
buf[0x32..0x3a].copy_from_slice(&container.to_le_bytes());
buf[0x42..0x46].copy_from_slice(&placement_kind.to_le_bytes());
buf[RECORD_MARKER_OFFSET..RECORD_MARKER_OFFSET + RECORD_MARKER.len()]
.copy_from_slice(&RECORD_MARKER);
buf[RECORD_MARKER_OFFSET + RECORD_MARKER.len()
..RECORD_MARKER_OFFSET + RECORD_MARKER.len() + 2]
.copy_from_slice(&2u16.to_le_bytes());
buf
}
fn synth_block(owner: u64, name: &str, elevation: f64, gap: usize) -> (Vec<u8>, usize) {
let units: Vec<u16> = name.encode_utf16().collect();
let run_start = 64usize;
let value = run_start + OWNER_OFFSET_BEFORE_NAME - 8;
let end = value + units.len() * 2;
let marker = end + gap;
let total = marker + ELEVATION_OFFSET_AFTER_MARKER + ELEVATION_CONFIRM_STRIDE + 8 + 16;
let mut buf = vec![0u8; total];
buf[run_start - 8..run_start].copy_from_slice(&owner.to_le_bytes());
for byte in buf.iter_mut().skip(run_start).take(OWNER_SENTINEL_RUN_LEN) {
*byte = 0xff;
}
buf[value - LENGTH_PREFIX_OFFSET_BEFORE_NAME..value]
.copy_from_slice(&(units.len() as u32).to_le_bytes());
for (index, unit) in units.iter().enumerate() {
let at = value + index * 2;
buf[at..at + 2].copy_from_slice(&unit.to_le_bytes());
}
buf[marker..marker + ELEVATION_MARKER.len()].copy_from_slice(&ELEVATION_MARKER);
let first = marker + ELEVATION_OFFSET_AFTER_MARKER;
buf[first..first + 8].copy_from_slice(&elevation.to_le_bytes());
let second = first + ELEVATION_CONFIRM_STRIDE;
buf[second..second + 8].copy_from_slice(&elevation.to_le_bytes());
(buf, run_start)
}
#[test]
fn decodes_a_standalone_level_record() {
let buf = synth_record(20268, CONTAINER_NONE, PLACEMENT_KIND_INSTANCE);
let record =
decode_record_at("Partitions/46", &buf, 0, &declared(&[20268])).expect("decodes");
assert_eq!(record.element_id, 20268);
assert_eq!(record.flags, 0x87);
assert!(!record.is_container_member());
assert!(record.is_placed_instance());
assert!(record.is_level_element());
}
#[test]
fn a_container_member_or_symbol_is_not_a_level_element() {
let member = synth_record(16230, 16_229, PLACEMENT_KIND_INSTANCE);
let record =
decode_record_at("Partitions/46", &member, 0, &declared(&[16230])).expect("decodes");
assert!(record.is_container_member());
assert!(!record.is_level_element());
let symbol = synth_record(1673, CONTAINER_NONE, PLACEMENT_KIND_SYMBOL);
let record =
decode_record_at("Partitions/46", &symbol, 0, &declared(&[1673])).expect("decodes");
assert!(!record.is_placed_instance());
assert!(!record.is_level_element());
}
#[test]
fn a_record_without_the_level_marker_is_rejected() {
let mut buf = synth_record(20268, CONTAINER_NONE, PLACEMENT_KIND_INSTANCE);
buf[RECORD_MARKER_OFFSET] = 0x46;
assert!(decode_record_at("Partitions/46", &buf, 0, &declared(&[20268])).is_none());
}
#[test]
fn a_record_whose_id_is_not_declared_is_rejected() {
let buf = synth_record(20268, CONTAINER_NONE, PLACEMENT_KIND_INSTANCE);
assert!(decode_record_at("Partitions/46", &buf, 0, &declared(&[9999])).is_none());
}
#[test]
fn scan_finds_a_record_at_any_offset() {
let mut buf = vec![0u8; 23];
buf.extend(synth_record(20268, CONTAINER_NONE, PLACEMENT_KIND_INSTANCE));
let found = find_level_records("Partitions/46", &buf, &declared(&[20268]));
assert_eq!(found.len(), 1);
assert_eq!(found[0].offset, 23);
}
#[test]
fn decodes_a_well_formed_name_block() {
let (buf, run) = synth_block(20273, "Basement 2", -40.0, 347);
let block = decode_name_block_at(&buf, run, &declared(&[20273])).expect("decodes");
assert_eq!(block.element_id, 20273);
assert_eq!(block.name, "Basement 2");
assert_eq!(block.elevation_feet, -40.0);
}
#[test]
fn the_gap_between_the_name_and_the_marker_is_not_fixed() {
let (buf, run) = synth_block(65128, "Level 13", 185.5, 363);
let block = decode_name_block_at(&buf, run, &declared(&[65128])).expect("decodes");
assert_eq!(block.elevation_feet, 185.5);
}
#[test]
fn disagreeing_elevation_copies_are_rejected() {
let (mut buf, run) = synth_block(20272, "Basement 1", -20.0, 347);
let end = run + OWNER_OFFSET_BEFORE_NAME - 8 + 2 * "Basement 1".len();
let marker = end + 347;
let second = marker + ELEVATION_OFFSET_AFTER_MARKER + ELEVATION_CONFIRM_STRIDE;
buf[second..second + 8].copy_from_slice(&0.0f64.to_le_bytes());
assert!(decode_name_block_at(&buf, run, &declared(&[20272])).is_none());
}
#[test]
fn an_owner_absent_from_elem_table_is_rejected() {
let (buf, run) = synth_block(20273, "Basement 2", -40.0, 347);
assert!(decode_name_block_at(&buf, run, &declared(&[9999])).is_none());
}
#[test]
fn a_block_with_no_elevation_marker_is_rejected() {
let (mut buf, run) = synth_block(20273, "Basement 2", -40.0, 347);
let end = run + OWNER_OFFSET_BEFORE_NAME - 8 + 2 * "Basement 2".len();
buf[end + 347] = 0x06;
assert!(decode_name_block_at(&buf, run, &declared(&[20273])).is_none());
}
#[test]
fn the_scan_finds_a_block_and_the_join_keeps_only_level_owners() {
let (buf, _) = synth_block(20273, "Basement 2", -40.0, 347);
let blocks = find_name_blocks(&buf, &declared(&[20273]));
assert_eq!(blocks.len(), 1);
let records = vec![
decode_record_at(
"Partitions/46",
&synth_record(20273, CONTAINER_NONE, PLACEMENT_KIND_INSTANCE),
0,
&declared(&[20273]),
)
.expect("decodes"),
];
let levels = levels_from_records_and_blocks(&records, blocks.clone());
assert_eq!(levels.len(), 1);
assert_eq!(levels[0].name, "Basement 2");
let members = vec![
decode_record_at(
"Partitions/46",
&synth_record(20273, 21_920, PLACEMENT_KIND_INSTANCE),
0,
&declared(&[20273]),
)
.expect("decodes"),
];
assert!(levels_from_records_and_blocks(&members, blocks).is_empty());
}
#[test]
fn a_level_naming_two_different_elevations_is_dropped() {
let records = vec![
decode_record_at(
"Partitions/46",
&synth_record(20273, CONTAINER_NONE, PLACEMENT_KIND_INSTANCE),
0,
&declared(&[20273]),
)
.expect("decodes"),
];
let block = |elevation: f64| NameElevationBlock {
element_id: 20273,
name: "Basement 2".into(),
elevation_feet: elevation,
building_story: None,
};
assert!(levels_from_records_and_blocks(&records, [block(-40.0), block(-20.0)]).is_empty());
assert_eq!(
levels_from_records_and_blocks(&records, [block(-40.0), block(-40.0)]).len(),
1
);
}
#[test]
fn a_partial_or_degenerate_recovery_is_not_a_storey_set() {
let record = |id: u32| {
decode_record_at(
"Partitions/46",
&synth_record(id, CONTAINER_NONE, PLACEMENT_KIND_INSTANCE),
0,
&declared(&[id]),
)
.expect("decodes")
};
let records = vec![record(20268), record(20272), record(20273)];
let level = |id: u32, elevation: f64| PartitionLevel {
element_id: id,
name: format!("L{id}"),
elevation_feet: elevation,
building_story: None,
};
assert!(!recovered_levels_are_a_storey_set(
&records,
&[level(20268, 0.0), level(20272, -20.0)]
));
assert!(!recovered_levels_are_a_storey_set(
&records,
&[level(20268, 0.0), level(20272, 0.0), level(20273, -40.0)]
));
assert!(recovered_levels_are_a_storey_set(
&records,
&[level(20268, 0.0), level(20272, -20.0), level(20273, -40.0)]
));
}
#[test]
fn unsupported_release_yields_nothing() {
assert!(!supports_revit_version(2022));
assert!(supports_revit_version(2023));
assert!(supports_revit_version(2024));
assert!(supports_revit_version(2025));
assert!(elevation_marker(2023).is_none() && record_marker(2023).is_none());
}
#[test]
fn markers_follow_the_release() {
assert_eq!(record_marker(2024), Some(RECORD_MARKER));
assert_eq!(
record_marker(2025),
Some([0xff, 0xff, 0xff, 0xff, 0xd3, 0x05])
);
assert_eq!(elevation_marker(2024).unwrap()[..], ELEVATION_MARKER[..6]);
assert_eq!(
elevation_marker(2025),
Some([0x05, 0x00, 0x00, 0x00, 0x5d, 0x02])
);
}
fn snowdon_block(
owner: u64,
name: &str,
elevation: f64,
lead_in: [u8; 24],
) -> (Vec<u8>, usize) {
let (mut buf, run_start) = synth_block(owner, name, elevation, 13);
let value = run_start + OWNER_OFFSET_BEFORE_NAME - 8;
let marker = value + name.encode_utf16().count() * 2 + 13;
buf.resize(marker + 400, 0);
buf[marker + 6..marker + 14].copy_from_slice(&(-90.00212f64).to_le_bytes());
let first = marker + ELEVATION_OFFSET_AFTER_MARKER;
buf[first - 24..first].copy_from_slice(&lead_in);
let core_copy = first + ELEVATION_CONFIRM_STRIDE;
buf[core_copy..core_copy + 8].fill(0);
let copy = first + 161;
buf[copy - 24..copy].copy_from_slice(&lead_in);
buf[copy..copy + 8].copy_from_slice(&elevation.to_le_bytes());
(buf, run_start)
}
#[test]
fn the_confirming_copy_is_found_by_its_lead_in() {
let lead_in = [
0x79, 0x51, 0xe6, 0xcc, 0x7f, 0x5c, 0x40, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0x85, 0xc5,
0x2a, 0x15, 0xa0, 0xf7, 0x41, 0x3d,
];
let (buf, run_start) = snowdon_block(593147, "L3", 18.833333333333, lead_in);
let marker = elevation_marker(2024).unwrap();
let block = decode_name_block_at_with(&buf, run_start, &declared(&[593147]), &marker)
.expect("decodes");
assert_eq!(block.name, "L3");
assert_eq!(block.elevation_feet, 18.833333333333);
assert!(find_subslice(&buf, &ELEVATION_MARKER).is_none());
assert_eq!(
decode_name_block_at(&buf, run_start, &declared(&[593147])),
Some(block)
);
}
#[test]
fn a_copy_behind_other_bytes_does_not_confirm() {
let lead_in = [7u8; 24];
let (mut buf, run_start) = snowdon_block(593147, "L3", 18.833333333333, lead_in);
let value = run_start + OWNER_OFFSET_BEFORE_NAME - 8;
let copy = value + 4 + 13 + ELEVATION_OFFSET_AFTER_MARKER + 161;
buf[copy - 1] = 8;
let marker = elevation_marker(2024).unwrap();
assert!(
decode_name_block_at_with(&buf, run_start, &declared(&[593147]), &marker).is_none()
);
}
#[test]
fn a_level_whose_block_names_an_owner_is_owned() {
let name: Vec<u16> = "Ref. Level".encode_utf16().collect();
let mut buf = vec![0xffu8; 0x120];
let id_at = 0x20;
buf[id_at - 4..id_at].copy_from_slice(&1u32.to_le_bytes());
buf[id_at..id_at + 8].copy_from_slice(&1_982_117u64.to_le_bytes());
buf[id_at + 16..id_at + 24].copy_from_slice(&1_982_107u64.to_le_bytes());
let value = id_at + OWNER_OFFSET_BEFORE_NAME;
buf[value - 4..value].copy_from_slice(&(name.len() as u32).to_le_bytes());
for (i, unit) in name.iter().enumerate() {
buf[value + 2 * i..value + 2 * i + 2].copy_from_slice(&unit.to_le_bytes());
}
let levels = declared(&[1_982_117, 593_147]);
assert_eq!(owned_level_ids(&buf, &levels), declared(&[1_982_117]));
buf[id_at + 16..id_at + 24].fill(0xff);
assert!(owned_level_ids(&buf, &levels).is_empty());
}
#[test]
fn a_second_prologue_level_takes_its_records_id() {
use crate::partition_element_records::PartitionRecordSpan;
let mut buf = vec![0u8; 0x40];
let frame = synth_record(1, CONTAINER_NONE, PLACEMENT_KIND_INSTANCE);
let at = buf.len();
buf.extend(frame);
buf[at..at + 8].copy_from_slice(&u64::MAX.to_le_bytes());
let chain = [PartitionRecordSpan {
start: 0,
end: buf.len(),
element_id: 828_874,
}];
let record = decode_record_at_with(
"Partitions/1",
&buf,
at,
&declared(&[828_874]),
&RECORD_MARKER,
&chain,
)
.expect("decodes");
assert_eq!(record.element_id, 828_874);
assert!(decode_record_at("Partitions/1", &buf, at, &declared(&[828_874])).is_none());
}
}