///|
/// Parse once and prepare the indexes commonly needed by BIM applications.
pub fn ModelWorkspace::open(source : String) -> ModelWorkspace raise ParseError {
let model = parse(source)
let catalog = ModelCatalog::new(model)
let spatial = SpatialIndex::new(model)
let graph = analyze_entity_graph(entity_graph(model))
{
model,
catalog,
spatial,
graph,
health: source_health(source),
quantities: None,
}
}
///|
pub fn ModelWorkspace::from_model(
model : Model,
source? : String = "",
) -> ModelWorkspace raise ParseError {
let catalog = ModelCatalog::new(model)
let spatial = SpatialIndex::new(model)
let graph = analyze_entity_graph(entity_graph(model))
let actual_source = if source.length() == 0 {
model.to_step()
} else {
source
}
{
model,
catalog,
spatial,
graph,
health: source_health(actual_source),
quantities: None,
}
}
///|
pub fn workspace(source : String) -> ModelWorkspace raise ParseError {
ModelWorkspace::open(source)
}
///|
pub fn ModelWorkspace::model(self : ModelWorkspace) -> Model {
self.model
}
///|
pub fn ModelWorkspace::catalog(self : ModelWorkspace) -> ModelCatalog {
self.catalog
}
///|
pub fn ModelWorkspace::spatial_index(self : ModelWorkspace) -> SpatialIndex {
self.spatial
}
///|
pub fn ModelWorkspace::graph_analysis(self : ModelWorkspace) -> GraphAnalysis {
self.graph
}
///|
pub fn ModelWorkspace::source_health(self : ModelWorkspace) -> SourceHealth {
self.health
}
///|
pub fn ModelWorkspace::with_quantities(
self : ModelWorkspace,
options? : TakeoffOptions = default_takeoff_options(),
) -> ModelWorkspace raise ParseError {
{ ..self, quantities: Some(quantity_takeoff(self.model, options~)) }
}
///|
pub fn ModelWorkspace::has_quantities(self : ModelWorkspace) -> Bool {
self.quantities is Some(_)
}
///|
pub fn ModelWorkspace::takeoff(
self : ModelWorkspace,
options? : TakeoffOptions = default_takeoff_options(),
) -> TakeoffReport raise ParseError {
match self.quantities {
Some(report) => report
None => quantity_takeoff(self.model, options~)
}
}
///|
pub fn ModelWorkspace::query(
self : ModelWorkspace,
plan : ProductQueryPlan,
) -> ProductQueryPage raise ParseError {
plan.execute(self.model)
}
///|
pub fn ModelWorkspace::find(
self : ModelWorkspace,
product_id : Int,
) -> Product? {
self.catalog.products.filter(product => product.id == product_id).get(0)
}
///|
pub fn ModelWorkspace::products(self : ModelWorkspace) -> Array[Product] {
self.catalog.products.copy()
}
///|
pub fn ModelWorkspace::clashes(
self : ModelWorkspace,
options? : ClashOptions = default_clash_options(),
) -> ClashReport {
detect_clashes(self.spatial, options~)
}
///|
pub fn ModelWorkspace::rules(
self : ModelWorkspace,
rules : Array[ModelRule],
) -> RuleReport raise ParseError {
evaluate_rules(self.model, rules)
}
///|
pub fn ModelWorkspace::delivery_report(
self : ModelWorkspace,
) -> RuleReport raise ParseError {
delivery_rule_report(self.model)
}
///|
pub fn ModelWorkspace::snapshot(self : ModelWorkspace) -> ModelSnapshot {
model_snapshot(self.model)
}
///|
pub fn ModelWorkspace::summary(
self : ModelWorkspace,
) -> WorkspaceSummary raise ParseError {
let report = delivery_rule_report(self.model)
{
schema: self.model.schema(),
source_length: self.health.byte_length,
entity_count: self.model.entities.length(),
product_count: self.catalog.products.length(),
indexed_products: self.spatial.entries().length(),
graph_nodes: self.graph.nodes,
graph_edges: self.graph.edges,
rule_errors: report.error_count,
rule_warnings: report.warning_count,
}
}
///|
pub fn WorkspaceSummary::headline(self : WorkspaceSummary) -> String {
"schema=" +
self.schema +
" entities=" +
self.entity_count.to_string() +
" products=" +
self.product_count.to_string() +
" indexed=" +
self.indexed_products.to_string() +
" graph=" +
self.graph_nodes.to_string() +
"/" +
self.graph_edges.to_string() +
" rules=" +
self.rule_errors.to_string() +
"/" +
self.rule_warnings.to_string()
}
///|
pub fn WorkspaceSummary::is_healthy(self : WorkspaceSummary) -> Bool {
self.rule_errors == 0
}
///|
pub fn WorkspaceSummary::to_json(self : WorkspaceSummary) -> String {
"{\"schema\":\"" +
self.schema +
"\",\"source_length\":" +
self.source_length.to_string() +
",\"entity_count\":" +
self.entity_count.to_string() +
",\"product_count\":" +
self.product_count.to_string() +
",\"indexed_products\":" +
self.indexed_products.to_string() +
",\"graph_nodes\":" +
self.graph_nodes.to_string() +
",\"graph_edges\":" +
self.graph_edges.to_string() +
",\"rule_errors\":" +
self.rule_errors.to_string() +
",\"rule_warnings\":" +
self.rule_warnings.to_string() +
"}"
}
///|
pub fn model_snapshot(model : Model) -> ModelSnapshot {
{
schema: model.schema(),
source_length: model.to_step().length(),
entity_count: model.entities.length(),
product_count: model.products().length(),
shape_signatures: model_shape_signatures(model),
reference_targets: model_reference_targets(model),
}
}
///|
pub fn ModelSnapshot::same_shape(
self : ModelSnapshot,
other : ModelSnapshot,
) -> Bool {
self.shape_signatures == other.shape_signatures
}
///|
pub fn ModelSnapshot::same_references(
self : ModelSnapshot,
other : ModelSnapshot,
) -> Bool {
self.reference_targets == other.reference_targets
}
///|
pub fn ModelSnapshot::entity_delta(
self : ModelSnapshot,
other : ModelSnapshot,
) -> Int {
other.entity_count - self.entity_count
}
///|
pub fn ModelSnapshot::product_delta(
self : ModelSnapshot,
other : ModelSnapshot,
) -> Int {
other.product_count - self.product_count
}
///|
pub fn ModelSnapshot::headline(self : ModelSnapshot) -> String {
self.schema +
" entities=" +
self.entity_count.to_string() +
" products=" +
self.product_count.to_string() +
" source=" +
self.source_length.to_string()
}
///|
pub fn ModelSnapshot::to_json(self : ModelSnapshot) -> String {
"{\"schema\":\"" +
self.schema +
"\",\"source_length\":" +
self.source_length.to_string() +
",\"entity_count\":" +
self.entity_count.to_string() +
",\"product_count\":" +
self.product_count.to_string() +
",\"shape_count\":" +
self.shape_signatures.length().to_string() +
",\"reference_target_count\":" +
self.reference_targets.length().to_string() +
"}"
}
///|
pub fn snapshot_diff(
left : ModelSnapshot,
right : ModelSnapshot,
) -> Array[String] {
let result : Array[String] = []
if left.schema != right.schema {
result.push("schema changed: " + left.schema + " -> " + right.schema)
}
if left.entity_count != right.entity_count {
result.push("entities delta: " + left.entity_delta(right).to_string())
}
if left.product_count != right.product_count {
result.push("products delta: " + left.product_delta(right).to_string())
}
if !left.same_shape(right) {
result.push("shape signatures changed")
}
if !left.same_references(right) {
result.push("reference targets changed")
}
result
}
///|
pub fn workspace_snapshot_diff(
left : ModelWorkspace,
right : ModelWorkspace,
) -> Array[String] {
snapshot_diff(left.snapshot(), right.snapshot())
}
///|
pub fn workspace_export(
workspace : ModelWorkspace,
options? : ExportOptions = default_export_options(),
) -> ExportedAsset raise ParseError {
export_model_asset(workspace.model, options)
}
///|
pub fn workspace_inspection(workspace : ModelWorkspace) -> Inspection {
{
model: workspace.model,
summary: workspace.catalog.summary(),
metrics: workspace.catalog.metrics,
validation: workspace.model.validate(),
capabilities: capability_report(workspace.model),
}
}
///|
pub fn workspace_health_score(workspace : ModelWorkspace) -> Int {
source_health_score(workspace.health_to_source())
}
///|
fn ModelWorkspace::health_to_source(self : ModelWorkspace) -> String {
self.model.to_step()
}
///|
pub fn ModelWorkspace::headline(
self : ModelWorkspace,
) -> String raise ParseError {
self.summary().headline()
}
///|
pub fn ModelWorkspace::to_json(
self : ModelWorkspace,
) -> String raise ParseError {
let summary = self.summary()
"{\"summary\":" +
summary.to_json() +
",\"health\":\"" +
self.health.summary() +
"\"" +
",\"graph\":" +
self.graph.to_json() +
",\"spatial\":" +
self.spatial.to_json() +
"}"
}
///|
pub fn workspace_query_by_kind(
workspace : ModelWorkspace,
kind : ProductKind,
) -> Array[Product] raise ParseError {
workspace.query(ProductQueryPlan::new().where_predicate(ProductKind(kind))).products
}
///|
pub fn workspace_query_by_type(
workspace : ModelWorkspace,
ifc_type : String,
) -> Array[Product] raise ParseError {
workspace.query(ProductQueryPlan::new().where_predicate(EntityType(ifc_type))).products
}
///|
pub fn workspace_products_at_point(
workspace : ModelWorkspace,
point : Point3,
tolerance? : Double = 0.0,
) -> Array[SpatialHit] {
workspace.spatial.query_point(point, tolerance~)
}
///|
pub fn workspace_nearest_product(
workspace : ModelWorkspace,
point : Point3,
) -> SpatialHit? {
workspace.spatial.nearest(point)
}