///|
fn material_name_for_optional(index : ModelIndex, id : Int?) -> String? {
match id {
Some(value) => material_entity_name(index, value)
None => None
}
}
///|
fn optional_value_double(value : Value?) -> Double? {
match value {
Some(item) => item.as_double()
None => None
}
}
///|
fn optional_value_int(value : Value?) -> Int? {
match value {
Some(item) => item.as_int()
None => None
}
}
///|
fn material_entity_name(index : ModelIndex, id : Int) -> String? {
match index.entity(id) {
Some(entity) if entity.name.to_upper() == "IFCMATERIAL" =>
optional_string_argument(entity, 0)
Some(entity) if entity.name.to_upper() == "IFCMATERIALDEFINITION" =>
optional_string_argument(entity, 0)
_ => None
}
}
///|
fn material_layer_from_entity(
index : ModelIndex,
entity : Entity,
) -> MaterialLayer {
let material_id = optional_reference_argument(entity, 0)
{
entity_id: entity.id,
material_id,
material_name: material_name_for_optional(index, material_id),
thickness: optional_value_double(entity_value(entity, 1)),
priority: optional_value_int(entity_value(entity, 2)),
is_unbounded: entity.name.to_upper() == "IFCMATERIALCONSTITUENT",
}
}
///|
fn material_layer_ids(index : ModelIndex, id : Int) -> Array[Int] {
match index.entity(id) {
Some(entity) if entity.name.to_upper() == "IFCMATERIALLAYERSET" =>
entity_value(entity, 0).map(value => references(value)).unwrap_or([])
Some(entity) if entity.name.to_upper() == "IFCMATERIALLAYERSETUSAGE" =>
optional_reference_argument(entity, 0)
.map(layer_id => material_layer_ids(index, layer_id))
.unwrap_or([])
_ => []
}
}
///|
fn material_constituent_names(index : ModelIndex, id : Int) -> Array[String] {
match index.entity(id) {
Some(entity) if entity.name.to_upper() == "IFCMATERIALCONSTITUENTSET" =>
entity_value(entity, 0)
.map(value => {
references(value).fold(init=Array::new(), (result, constituent_id) => {
match index.entity(constituent_id) {
Some(constituent) =>
match
material_name_for_optional(
index,
optional_reference_argument(constituent, 0),
) {
Some(name) => result.push(name)
None => ()
}
None => ()
}
result
})
})
.unwrap_or([])
_ => []
}
}
///|
fn material_assignment_for(
index : ModelIndex,
product_id : Int,
relation : Entity,
) -> MaterialAssignment {
let material_id = optional_reference_argument(relation, 5)
let material_name = material_name_for_optional(index, material_id)
let layers : Array[MaterialLayer] = match material_id {
Some(id) =>
match index.entity(id) {
Some(_) => {
let layer_ids = material_layer_ids(index, id)
layer_ids.fold(init=Array::new(), (result, layer_id) => {
match index.entity(layer_id) {
Some(layer) =>
result.push(material_layer_from_entity(index, layer))
None => ()
}
result
})
}
None => []
}
None => []
}
let constituents = material_id
.map(id => material_constituent_names(index, id))
.unwrap_or([])
{
product_id,
association_id: relation.id,
material_id,
material_name,
layers,
constituents,
}
}
///|
/// Decode material associations for one product.
pub fn material_assignments(
index : ModelIndex,
product : Product,
) -> Array[MaterialAssignment] {
index
.all()
.fold(init=Array::new(), (result, relation) => {
if relation.name.to_upper() == "IFCRELASSOCIATESMATERIAL" &&
relation_objects(relation, 4).contains(product.id) {
result.push(material_assignment_for(index, product.id, relation))
}
result
})
}
///|
pub fn materials_for_product(
index : ModelIndex,
product : Product,
) -> Array[String] {
let result : Array[String] = []
for assignment in material_assignments(index, product) {
match assignment.material_name {
Some(name) if !result.contains(name) => result.push(name)
_ => ()
}
for layer in assignment.layers {
match layer.material_name {
Some(name) if !result.contains(name) => result.push(name)
_ => ()
}
}
for name in assignment.constituents {
if !result.contains(name) {
result.push(name)
}
}
}
result
}
///|
pub fn material_layers_for_product(
index : ModelIndex,
product : Product,
) -> Array[MaterialLayer] {
material_assignments(index, product).fold(init=Array::new(), (
result,
assignment,
) => {
result.append(assignment.layers)
result
})
}
///|
pub fn material_thickness(index : ModelIndex, product : Product) -> Double {
material_layers_for_product(index, product).fold(init=0.0, (result, layer) => {
result + layer.thickness.unwrap_or(0.0)
})
}
///|
pub fn MaterialLayer::headline(self : MaterialLayer) -> String {
self.material_name.unwrap_or("(unknown)") +
" thickness=" +
self.thickness.map(value => value.to_string()).unwrap_or("-")
}
///|
pub fn MaterialAssignment::material_names(
self : MaterialAssignment,
) -> Array[String] {
let result : Array[String] = []
match self.material_name {
Some(name) => result.push(name)
None => ()
}
for layer in self.layers {
match layer.material_name {
Some(name) if !result.contains(name) => result.push(name)
_ => ()
}
}
for name in self.constituents {
if !result.contains(name) {
result.push(name)
}
}
result
}
///|
pub fn MaterialAssignment::headline(self : MaterialAssignment) -> String {
"product=#" +
self.product_id.to_string() +
" association=#" +
self.association_id.to_string() +
" materials=" +
self.material_names().join(", ") +
" layers=" +
self.layers.length().to_string()
}
///|
fn material_inventory_materials(
assignments : Array[MaterialAssignment],
) -> Array[String] {
let result : Array[String] = []
for assignment in assignments {
for name in assignment.material_names() {
if !result.contains(name) {
result.push(name)
}
}
}
result
}
///|
/// Build an inventory used by material schedules and specification checks.
pub fn material_inventory(model : Model) -> MaterialInventory {
let index = model.index()
let assignments : Array[MaterialAssignment] = []
let mut layered = 0
let mut unassigned = 0
for product in model.products() {
let product_assignments = material_assignments(index, product)
if product_assignments.length() == 0 {
unassigned += 1
} else {
for assignment in product_assignments {
if assignment.layers.length() > 0 {
layered += 1
}
assignments.push(assignment)
}
}
}
{
assignments,
distinct_materials: material_inventory_materials(assignments),
layered_product_count: layered,
unassigned_product_count: unassigned,
}
}
///|
pub fn MaterialInventory::assignment_count(self : MaterialInventory) -> Int {
self.assignments.length()
}
///|
pub fn MaterialInventory::material_count(self : MaterialInventory) -> Int {
self.distinct_materials.length()
}
///|
pub fn MaterialInventory::products_with_material(
self : MaterialInventory,
name : String,
) -> Array[Int] {
let wanted = name.to_lower()
let result : Array[Int] = []
for assignment in self.assignments {
if assignment.material_names().any(value => value.to_lower() == wanted) &&
!result.contains(assignment.product_id) {
result.push(assignment.product_id)
}
}
result
}
///|
pub fn MaterialInventory::thickness_for_product(
self : MaterialInventory,
product_id : Int,
) -> Double {
self.assignments.fold(init=0.0, (result, assignment) => {
if assignment.product_id == product_id {
result +
assignment.layers.fold(init=0.0, (value, layer) => {
value + layer.thickness.unwrap_or(0.0)
})
} else {
result
}
})
}
///|
pub fn MaterialInventory::headline(self : MaterialInventory) -> String {
"materials=" +
self.distinct_materials.length().to_string() +
" assignments=" +
self.assignments.length().to_string() +
" layered_products=" +
self.layered_product_count.to_string() +
" unassigned_products=" +
self.unassigned_product_count.to_string()
}
///|
pub fn MaterialInventory::to_json(self : MaterialInventory) -> String {
"{\"material_count\":" +
self.distinct_materials.length().to_string() +
",\"layered_product_count\":" +
self.layered_product_count.to_string() +
",\"unassigned_product_count\":" +
self.unassigned_product_count.to_string() +
",\"materials\":[" +
self.distinct_materials.map(name => "\"" + name + "\"").join(",") +
"]}"
}
///|
pub fn MaterialInventory::to_csv(self : MaterialInventory) -> String {
let builder = StringBuilder()
builder <+ "product_id,association_id,material,layers,thickness\n"
for assignment in self.assignments {
let names = assignment.material_names().join(";")
let thickness = assignment.layers.fold(init=0.0, (result, layer) => {
result + layer.thickness.unwrap_or(0.0)
})
builder <+
"\{assignment.product_id},\{assignment.association_id},\{names},\{assignment.layers.length()},\{thickness}\n"
}
builder.to_string()
}
///|
fn classification_reference_from_entity(
index : ModelIndex,
entity : Entity,
product_id : Int,
) -> ClassificationReference {
let source_id = optional_reference_argument(entity, 0)
let source_name = match source_id {
Some(id) =>
match index.entity(id) {
Some(source) => optional_string_argument(source, 0)
None => None
}
None => None
}
{
entity_id: entity.id,
source: source_name,
identification: optional_string_argument(entity, 1),
name: optional_string_argument(entity, 2),
referenced_entity: product_id,
}
}
///|
/// Decode classification associations for one product.
pub fn classification_references(
index : ModelIndex,
product : Product,
) -> Array[ClassificationReference] {
let result : Array[ClassificationReference] = []
for relation in index.all() {
if relation.name.to_upper() == "IFCRELASSOCIATESCLASSIFICATION" &&
relation_objects(relation, 4).contains(product.id) {
match optional_reference_argument(relation, 5) {
Some(id) =>
match index.entity(id) {
Some(reference) =>
result.push(
classification_reference_from_entity(
index,
reference,
product.id,
),
)
None => ()
}
None => ()
}
}
}
result
}
///|
pub fn classification_codes(
index : ModelIndex,
product : Product,
) -> Array[String] {
classification_references(index, product).fold(init=Array::new(), (
result,
reference,
) => {
match reference.identification {
Some(value) => result.push(value)
None => ()
}
result
})
}
///|
pub fn classification_name(reference : ClassificationReference) -> String {
reference.name.unwrap_or(reference.identification.unwrap_or("(unnamed)"))
}
///|
pub fn ClassificationReference::headline(
self : ClassificationReference,
) -> String {
self.source.unwrap_or("(local)") +
":" +
self.identification.unwrap_or("(no-code)") +
" " +
classification_name(self)
}
///|
pub fn material_report_lines(inventory : MaterialInventory) -> Array[String] {
[
inventory.headline(),
"distinct=" + inventory.distinct_materials.join(", "),
"layered=" + inventory.layered_product_count.to_string(),
]
}