use super::level::normalise_field_name;
use crate::formats;
use crate::walker::{DecodedElement, ElementDecoder, HandleIndex, InstanceField};
use crate::{Error, Result};
macro_rules! simple_decoder {
($Struct:ident, $name:literal) => {
pub struct $Struct;
impl ElementDecoder for $Struct {
fn class_name(&self) -> &'static str {
$name
}
fn decode(
&self,
bytes: &[u8],
schema: &formats::ClassEntry,
_index: &HandleIndex,
) -> Result<DecodedElement> {
if schema.name != $name {
return Err(Error::BasicFileInfo(format!(
"{} received wrong schema: {}",
stringify!($Struct),
schema.name
)));
}
Ok(crate::walker::decode_instance(bytes, 0, schema))
}
}
};
}
simple_decoder!(CurtainWallDecoder, "CurtainWall");
simple_decoder!(CurtainGridDecoder, "CurtainGrid");
simple_decoder!(CurtainMullionDecoder, "CurtainMullion");
simple_decoder!(CurtainPanelDecoder, "CurtainPanel");
#[derive(Debug, Clone, PartialEq, Default)]
pub struct CurtainWall {
pub base_level_id: Option<u32>,
pub top_level_id: Option<u32>,
pub base_offset_feet: Option<f64>,
pub top_offset_feet: Option<f64>,
pub unconnected_height_feet: Option<f64>,
pub type_id: Option<u32>,
}
impl CurtainWall {
pub fn from_decoded(decoded: &DecodedElement) -> Self {
let mut out = Self::default();
for (field_name, value) in &decoded.fields {
match (normalise_field_name(field_name).as_str(), value) {
("levelid" | "baselevelid", InstanceField::ElementId { id, .. }) => {
out.base_level_id = Some(*id);
}
("toplevelid", InstanceField::ElementId { id, .. }) => {
out.top_level_id = Some(*id);
}
("baseoffset", InstanceField::Float { value, .. }) => {
out.base_offset_feet = Some(*value);
}
("topoffset", InstanceField::Float { value, .. }) => {
out.top_offset_feet = Some(*value);
}
("unconnectedheight", InstanceField::Float { value, .. }) => {
out.unconnected_height_feet = Some(*value);
}
("typeid", InstanceField::ElementId { id, .. }) => out.type_id = Some(*id),
_ => {}
}
}
out
}
pub fn is_unconnected(&self) -> bool {
matches!(self.top_level_id, None | Some(0))
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct CurtainGrid {
pub host_id: Option<u32>,
pub u_line_count: Option<u32>,
pub v_line_count: Option<u32>,
}
impl CurtainGrid {
pub fn from_decoded(decoded: &DecodedElement) -> Self {
let mut out = Self::default();
for (field_name, value) in &decoded.fields {
match (normalise_field_name(field_name).as_str(), value) {
("hostid", InstanceField::ElementId { id, .. }) => out.host_id = Some(*id),
("ugridlinecount" | "ucount", InstanceField::Integer { value, .. }) => {
out.u_line_count = Some(*value as u32);
}
("vgridlinecount" | "vcount", InstanceField::Integer { value, .. }) => {
out.v_line_count = Some(*value as u32);
}
_ => {}
}
}
out
}
pub fn cell_count(&self) -> Option<u32> {
let u = self.u_line_count?;
let v = self.v_line_count?;
Some(u.saturating_sub(1) * v.saturating_sub(1))
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct CurtainMullion {
pub host_id: Option<u32>,
pub symbol_id: Option<u32>,
pub is_vertical: Option<bool>,
}
impl CurtainMullion {
pub fn from_decoded(decoded: &DecodedElement) -> Self {
let mut out = Self::default();
for (field_name, value) in &decoded.fields {
match (normalise_field_name(field_name).as_str(), value) {
("hostid", InstanceField::ElementId { id, .. }) => out.host_id = Some(*id),
("symbolid" | "typeid", InstanceField::ElementId { id, .. }) => {
out.symbol_id = Some(*id);
}
("isvertical", InstanceField::Bool(b)) => out.is_vertical = Some(*b),
_ => {}
}
}
out
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct CurtainPanel {
pub host_id: Option<u32>,
pub symbol_id: Option<u32>,
pub u_index: Option<u32>,
pub v_index: Option<u32>,
}
impl CurtainPanel {
pub fn from_decoded(decoded: &DecodedElement) -> Self {
let mut out = Self::default();
for (field_name, value) in &decoded.fields {
match (normalise_field_name(field_name).as_str(), value) {
("hostid", InstanceField::ElementId { id, .. }) => out.host_id = Some(*id),
("symbolid" | "typeid", InstanceField::ElementId { id, .. }) => {
out.symbol_id = Some(*id);
}
("uindex", InstanceField::Integer { value, .. }) => {
out.u_index = Some(*value as u32);
}
("vindex", InstanceField::Integer { value, .. }) => {
out.v_index = Some(*value as u32);
}
_ => {}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn curtain_wall_rejects_wrong_schema() {
use crate::formats::ClassEntry;
let wrong = ClassEntry {
name: "Wall".into(),
offset: 0,
fields: vec![],
tag: None,
parent: None,
declared_field_count: None,
was_parent_only: false,
ancestor_tag: None,
};
assert!(
CurtainWallDecoder
.decode(&[], &wrong, &HandleIndex::new())
.is_err()
);
}
#[test]
fn curtain_wall_unconnected_detection() {
let hanging = CurtainWall {
top_level_id: Some(0),
unconnected_height_feet: Some(30.0),
..Default::default()
};
let connected = CurtainWall {
top_level_id: Some(5),
..Default::default()
};
assert!(hanging.is_unconnected());
assert!(!connected.is_unconnected());
}
#[test]
fn curtain_grid_cell_count() {
let g = CurtainGrid {
u_line_count: Some(4),
v_line_count: Some(3),
..Default::default()
};
assert_eq!(g.cell_count(), Some(6));
let degen = CurtainGrid {
u_line_count: Some(1),
v_line_count: Some(1),
..Default::default()
};
assert_eq!(degen.cell_count(), Some(0));
let missing = CurtainGrid::default();
assert_eq!(missing.cell_count(), None);
}
#[test]
fn curtain_mullion_orientation() {
let fields = vec![
(
"m_host_id".into(),
InstanceField::ElementId { tag: 0, id: 42 },
),
("m_is_vertical".into(), InstanceField::Bool(true)),
];
let decoded = DecodedElement {
id: None,
class: "CurtainMullion".into(),
fields,
byte_range: 0..0,
provenance: Default::default(),
};
let m = CurtainMullion::from_decoded(&decoded);
assert_eq!(m.host_id, Some(42));
assert_eq!(m.is_vertical, Some(true));
}
#[test]
fn curtain_panel_indexed() {
let fields = vec![
(
"m_u_index".into(),
InstanceField::Integer {
value: 2,
signed: false,
size: 4,
},
),
(
"m_v_index".into(),
InstanceField::Integer {
value: 1,
signed: false,
size: 4,
},
),
];
let decoded = DecodedElement {
id: None,
class: "CurtainPanel".into(),
fields,
byte_range: 0..0,
provenance: Default::default(),
};
let p = CurtainPanel::from_decoded(&decoded);
assert_eq!(p.u_index, Some(2));
assert_eq!(p.v_index, Some(1));
}
#[test]
fn empty_tolerance() {
let empty = DecodedElement {
id: None,
class: "CurtainWall".into(),
fields: vec![],
byte_range: 0..0,
provenance: Default::default(),
};
assert!(CurtainWall::from_decoded(&empty).base_level_id.is_none());
assert!(CurtainGrid::from_decoded(&empty).u_line_count.is_none());
assert!(CurtainMullion::from_decoded(&empty).host_id.is_none());
assert!(CurtainPanel::from_decoded(&empty).host_id.is_none());
}
#[test]
fn class_names() {
assert_eq!(CurtainWallDecoder.class_name(), "CurtainWall");
assert_eq!(CurtainGridDecoder.class_name(), "CurtainGrid");
assert_eq!(CurtainMullionDecoder.class_name(), "CurtainMullion");
assert_eq!(CurtainPanelDecoder.class_name(), "CurtainPanel");
}
}