///|
fn slice_model_with_ids(
model : Model,
root_ids : Array[Int],
include_dependencies : Bool,
) -> ModelSlice {
let graph = entity_graph(model)
let dependency_ids = if include_dependencies {
graph.reachable(root_ids)
} else {
root_ids.copy()
}
let selected_ids = dependency_ids.copy()
let entities = model.entities.filter(entity => {
selected_ids.contains(entity.id)
})
let omitted_ids = model.entities
.filter(entity => !selected_ids.contains(entity.id))
.map(entity => entity.id)
{
model: Model::new(header=model.header, entities~),
selected_ids,
dependency_ids,
omitted_ids,
root_ids,
}
}
///|
/// Create a dependency-closed model slice rooted at raw entity IDs.
pub fn model_slice(
model : Model,
root_ids : Array[Int],
include_dependencies? : Bool = true,
) -> ModelSlice {
slice_model_with_ids(model, root_ids, include_dependencies)
}
///|
pub fn model_slice_for_products(
model : Model,
product_ids : Array[Int],
include_dependencies? : Bool = true,
) -> ModelSlice {
model_slice(model, product_ids, include_dependencies~)
}
///|
pub fn model_slice_for_query(
model : Model,
plan : ProductQueryPlan,
include_dependencies? : Bool = true,
) -> ModelSlice raise ParseError {
let products = plan.execute(model).products
model_slice(model, products.map(product => product.id), include_dependencies~)
}
///|
pub fn model_slice_for_box(
model : Model,
bounds : BoundingBox,
include_dependencies? : Bool = true,
) -> ModelSlice raise ParseError {
let spatial = SpatialIndex::new(model)
let products = spatial.query_box(bounds).map(hit => hit.product_id)
model_slice(model, products, include_dependencies~)
}
///|
pub fn ModelSlice::entity(self : ModelSlice, id : Int) -> Entity? {
self.model.entities.filter(entity => entity.id == id).get(0)
}
///|
pub fn ModelSlice::contains(self : ModelSlice, id : Int) -> Bool {
self.selected_ids.contains(id)
}
///|
pub fn ModelSlice::root_count(self : ModelSlice) -> Int {
self.root_ids.length()
}
///|
pub fn ModelSlice::dependency_count(self : ModelSlice) -> Int {
self.dependency_ids.length()
}
///|
pub fn ModelSlice::omitted_count(self : ModelSlice) -> Int {
self.omitted_ids.length()
}
///|
pub fn ModelSlice::product_count(self : ModelSlice) -> Int {
self.model.products().length()
}
///|
pub fn ModelSlice::is_closed(self : ModelSlice) -> Bool {
let ids = self.selected_ids
self.model.entities.all(entity => {
entity_references(entity).all(id => ids.contains(id))
})
}
///|
pub fn ModelSlice::references_omitted(self : ModelSlice) -> Array[Int] {
let ids = self.selected_ids
let result : Array[Int] = []
for entity in self.model.entities {
for id in entity_references(entity) {
if !ids.contains(id) && !result.contains(id) {
result.push(id)
}
}
}
result
}
///|
pub fn ModelSlice::to_step(self : ModelSlice) -> String {
self.model.to_step()
}
///|
pub fn ModelSlice::round_trip(self : ModelSlice) -> Model raise ParseError {
parse(self.to_step())
}
///|
pub fn ModelSlice::headline(self : ModelSlice) -> String {
"roots=" +
self.root_ids.length().to_string() +
" selected=" +
self.selected_ids.length().to_string() +
" dependencies=" +
self.dependency_ids.length().to_string() +
" omitted=" +
self.omitted_ids.length().to_string() +
" closed=" +
self.is_closed().to_string()
}
///|
pub fn ModelSlice::summary(self : ModelSlice) -> Array[String] {
[
self.headline(),
"schema=" + self.model.schema(),
"products=" + self.model.products().length().to_string(),
"references_omitted=" + self.references_omitted().length().to_string(),
]
}
///|
pub fn ModelSlice::to_json(self : ModelSlice) -> String {
"{\"roots\":[" +
self.root_ids.map(id => id.to_string()).join(",") +
"],\"selected_ids\":[" +
self.selected_ids.map(id => id.to_string()).join(",") +
"],\"dependency_count\":" +
self.dependency_ids.length().to_string() +
",\"omitted_count\":" +
self.omitted_ids.length().to_string() +
",\"closed\":" +
self.is_closed().to_string() +
"}"
}
///|
pub fn ModelSlice::to_markdown(self : ModelSlice) -> String {
let builder = StringBuilder()
builder <+ "# Model slice\n\n"
builder <+ "- Roots: \{self.root_ids.length()}\n"
builder <+ "- Selected entities: \{self.selected_ids.length()}\n"
builder <+ "- Dependencies: \{self.dependency_ids.length()}\n"
builder <+ "- Omitted entities: \{self.omitted_ids.length()}\n"
builder <+ "- Dependency closed: \{self.is_closed()}\n"
builder.to_string()
}
///|
pub fn ModelSlice::ids_by_type(self : ModelSlice, name : String) -> Array[Int] {
let wanted = name.to_upper()
self.model.entities
.filter(entity => entity.name.to_upper() == wanted)
.map(entity => entity.id)
}
///|
pub fn ModelSlice::entities_by_type(
self : ModelSlice,
name : String,
) -> Array[Entity] {
let wanted = name.to_upper()
self.model.entities.filter(entity => entity.name.to_upper() == wanted)
}
///|
pub fn ModelSlice::products_by_kind(
self : ModelSlice,
kind : ProductKind,
) -> Array[Product] {
self.model.products().filter(product => product.kind == kind)
}
///|
pub fn ModelSlice::with_additional_roots(
self : ModelSlice,
ids : Array[Int],
include_dependencies? : Bool = true,
) -> ModelSlice {
let roots = self.root_ids.copy()
for id in ids {
if !roots.contains(id) {
roots.push(id)
}
}
slice_model_with_ids(self.model, roots, include_dependencies)
}
///|
pub fn ModelSlice::without_roots(
self : ModelSlice,
ids : Array[Int],
) -> ModelSlice {
let roots = self.root_ids.filter(id => !ids.contains(id))
slice_model_with_ids(self.model, roots, true)
}
///|
pub fn ModelSlice::difference(
self : ModelSlice,
other : ModelSlice,
) -> Array[Int] {
self.selected_ids.filter(id => !other.selected_ids.contains(id))
}
///|
pub fn slice_changed_entities(before : Model, after : Model) -> ModelSlice {
let ids = diff_models(before, after).changed_ids()
model_slice(after, ids, include_dependencies=true)
}