use energyml_core::identity;
use energyml_core::value::{FieldValue, Reflect};
use energyml_domain::ArraySource as ValueSource;
use energyml_domain::color::{Cascade, Decision, GraphicalSet, Paint, Request, Scope};
use energyml_domain::package_index::PackageIndex;
use energyml_geometry::fence;
use energyml_geometry::scene::{self, Placement, SceneObject};
use energyml_geometry::section;
use crate::model::{
CutShape, CutSource, ObjectId, Refused, SectionTrace, SurfaceCut, SurfaceRead, TraceRole,
};
use crate::read::Scene;
use crate::read::frame::{frame_of, to_scene};
use crate::read::grids::Grid;
pub fn uuid_of_uri(uri: &str) -> String {
energyml_core::identity::parse_address(uri).0
}
#[allow(clippy::result_large_err)]
pub fn read_surface(
source: &dyn ValueSource,
store: &str,
uri: &str,
sections: &[SectionTrace],
scene: &Scene<'_>,
) -> Result<SurfaceRead, Refused> {
read_surface_coloured(source, store, uri, sections, scene, Colouring::default())
}
#[derive(Clone, Copy, Default)]
pub struct Colouring<'a> {
pub set: Option<&'a GraphicalSet>,
pub index: Option<&'a PackageIndex>,
pub resolver: Option<&'a crate::read::ColourOf<'a>>,
}
#[allow(clippy::result_large_err)]
pub fn read_surface_coloured(
source: &dyn ValueSource,
store: &str,
uri: &str,
sections: &[SectionTrace],
scene: &Scene<'_>,
colours: Colouring<'_>,
) -> Result<SurfaceRead, Refused> {
Ok(cut_prepared(
&prepare_surface(source, store, uri, scene, colours)?,
sections,
))
}
#[derive(Clone, Debug)]
pub struct Prepared {
pub object: ObjectId,
pub role: TraceRole,
pub time: bool,
pub colour: Option<String>,
pub age: Option<[f64; 2]>,
pub feature: Option<String>,
pub points: Vec<[f64; 3]>,
pub triangles: Vec<[u32; 3]>,
pub nodes: Vec<u32>,
pub grid: Option<Grid>,
}
#[allow(clippy::result_large_err)]
pub fn prepare_surface(
source: &dyn ValueSource,
store: &str,
uri: &str,
scene: &Scene<'_>,
colours: Colouring<'_>,
) -> Result<Prepared, Refused> {
let mut object = ObjectId {
store: store.to_string(),
uri: uri.to_string(),
uuid: uuid_of_uri(uri),
title: String::new(),
qualified_type: String::new(),
};
let refuse = |object: &ObjectId, reason: String| Refused {
object: object.clone(),
reason,
};
let held = source
.read_object_at(uri)
.map_err(|why| refuse(&object, format!("`{uri}` cannot be read from the package: {why}")))?;
let representation = held.as_reflect();
let info = representation.type_info();
if let Some(uuid) = identity::uuid_of_reflect(representation) {
object.uuid = uuid.to_ascii_lowercase();
}
object.title = identity::title_of_reflect(representation)
.unwrap_or_default()
.to_string();
object.qualified_type = energyml_core::json::qualified_type_of(info);
if info.type_key == "IjkGridRepresentation" {
let frame = frame_of(source, representation).map_err(|why| refuse(&object, why))?;
let named = |why: String| format!("`{}` ({}): {why}", object.title, object.qualified_type);
let read = scene::object_of(representation, source, uri)
.map_err(|why| refuse(&object, named(format!("{why}"))))?;
let placed = read
.geometry_in(Placement::Projected)
.map_err(|why| refuse(&object, named(format!("{why}"))))?;
let patch = placed
.patches
.into_iter()
.next()
.ok_or_else(|| refuse(&object, named("the grid has no patch".to_string())))?;
let mut patch = patch;
to_scene(&mut patch.points, &frame, scene).map_err(|why| refuse(&object, named(why)))?;
let grid = Grid::from_patch(&patch).map_err(|why| refuse(&object, named(why)))?;
let colour = file_colour_of(colours, &object, source);
return Ok(Prepared {
role: role_of(representation, &read, source),
time: frame.time,
colour,
age: age_of_represented(representation, source),
feature: feature_of(&read),
object,
points: Vec::new(),
triangles: Vec::new(),
nodes: Vec::new(),
grid: Some(grid),
});
}
if !energyml_geometry::geometry::is_supported(representation) {
return Err(refuse(
&object,
format!(
"`{}` is a {}, which this build does not read as a shape",
object.title, object.qualified_type
),
));
}
let frame = frame_of(source, representation).map_err(|why| refuse(&object, why))?;
let read =
scene::object_of(representation, source, uri).map_err(|why| refuse(&object, format!("{why}")))?;
let drawn = read
.drawable_in(Placement::Projected)
.map_err(|why| refuse(&object, format!("{why}")))?;
if drawn.triangles.is_empty() {
return Err(refuse(
&object,
format!(
"`{}` ({}) has no triangles - {} point(s) and {} line segment(s): only a triangulated shape is cut",
object.title,
object.qualified_type,
drawn.points.len(),
drawn.lines.len()
),
));
}
let nodes = drawn_nodes(&read.geometry, drawn.points.len());
let mut points = drawn.points;
to_scene(&mut points, &frame, scene).map_err(|why| {
refuse(
&object,
format!("`{}` ({}): {why}", object.title, object.qualified_type),
)
})?;
let colour = file_colour_of(colours, &object, source);
Ok(Prepared {
role: role_of(representation, &read, source),
time: frame.time,
colour,
age: age_of_represented(representation, source),
feature: feature_of(&read),
object,
points,
triangles: drawn.triangles,
nodes,
grid: None,
})
}
pub fn drawn_nodes(geometry: &energyml_geometry::geometry::Geometry, kept: usize) -> Vec<u32> {
let finite: Vec<u32> = geometry
.patches
.iter()
.flat_map(|patch| patch.points.iter())
.enumerate()
.filter(|(_, point)| point.iter().all(|c| c.is_finite()))
.map(|(at, _)| at as u32)
.collect();
if finite.len() == kept { finite } else { Vec::new() }
}
pub fn cut_prepared(prepared: &Prepared, sections: &[SectionTrace]) -> SurfaceRead {
if let Some(grid) = &prepared.grid {
return SurfaceRead {
object: prepared.object.clone(),
role: prepared.role,
time: prepared.time,
colour: prepared.colour.clone(),
age: prepared.age,
feature: prepared.feature.clone(),
cuts: grid.cut(sections),
};
}
let traces: Vec<&[[f64; 2]]> = sections.iter().map(|one| one.trace.as_slice()).collect();
let all = section::cut_many_sourced(&prepared.points, &prepared.triangles, &traces);
let mut cuts = Vec::new();
for (trace, found) in sections.iter().zip(all) {
if found.is_empty() {
continue;
}
let made = fence::curve_sourced(&found);
let sources = sources_of(prepared, &made.sources).unwrap_or_default();
cuts.push(SurfaceCut {
section: trace.key,
shape: CutShape::Trace {
spans: made.curve.spans,
},
notes: made.curve.notes,
sources,
});
}
SurfaceRead {
object: prepared.object.clone(),
role: prepared.role,
time: prepared.time,
colour: prepared.colour.clone(),
age: prepared.age,
feature: prepared.feature.clone(),
cuts,
}
}
fn sources_of(prepared: &Prepared, sources: &[Vec<section::CutEnd>]) -> Option<Vec<Vec<CutSource>>> {
if prepared.nodes.is_empty() {
return None;
}
let number = |at: u32| prepared.nodes.get(at as usize).copied();
sources
.iter()
.map(|span| {
span.iter()
.map(|end| {
Some(CutSource {
a: number(end.a)?,
b: number(end.b)?,
t: end.t,
})
})
.collect::<Option<Vec<_>>>()
})
.collect()
}
pub fn file_colour(colouring: Colouring<'_>, uuid: &str, source: &dyn ValueSource) -> Option<String> {
let set = colouring.set?;
cascade_colour(set, colouring.index, uuid, source)
}
pub fn file_colour_of(
colouring: Colouring<'_>,
object: &ObjectId,
source: &dyn ValueSource,
) -> Option<String> {
if let Some(resolver) = colouring.resolver {
return resolver(&object.uri);
}
file_colour(colouring, &object.uuid, source)
}
fn cascade_colour(
set: &GraphicalSet,
index: Option<&PackageIndex>,
uuid: &str,
source: &dyn ValueSource,
) -> Option<String> {
let cascade = Cascade::new(set);
let decision = match index {
Some(index) => cascade
.inherited(index, source, uuid)
.ok()
.and_then(|walked| walked.resolution)
.map(|found| found.value),
None => {
let request = Request {
representation: uuid,
property: None,
kind_chain: &[],
element: Scope::WholeObject,
};
cascade.resolve(&request, source).ok().flatten()
}
};
match decision {
Some(Decision {
paint: Paint::Constant(colour),
..
}) => Some(colour.to_hex()),
_ => None,
}
}
fn role_of(representation: &dyn Reflect, read: &SceneObject, source: &dyn ValueSource) -> TraceRole {
let Some(related) = &read.interpretation else {
return TraceRole::Other;
};
let resolved = scene::interpretation_of(representation, source);
let key: &str = match &resolved {
Some(found) => found.as_reflect().type_info().type_key,
None => related.qualified_type.rsplit(['.', '=']).next().unwrap_or(""),
};
match key.strip_prefix("obj_").unwrap_or(key) {
"HorizonInterpretation" => TraceRole::Horizon,
"FaultInterpretation" => TraceRole::Fault,
_ => TraceRole::Other,
}
}
pub fn age_of(interpretation: &dyn Reflect) -> Option<[f64; 2]> {
let years = |object: &dyn Reflect, name: &str| -> Option<f64> {
match object.field(name)? {
FieldValue::Scalar(scalar) => scalar
.to_string()
.trim()
.parse::<i64>()
.ok()
.map(|y| y as f64 / 1_000_000.0),
_ => None,
}
};
let ordered = |a: f64, b: f64| [a.max(b), a.min(b)];
if let Some(FieldValue::Object(interval)) = interpretation.field("HasOccurredDuring") {
let bound = |name: &str| match interval.field(name)? {
FieldValue::Object(time) => years(time, "AgeOffsetAttribute"),
_ => None,
};
if let (Some(start), Some(stop)) = (bound("Start"), bound("End")) {
return Some(ordered(start, stop));
}
}
if let (Some(older), Some(younger)) = (
years(interpretation, "OlderPossibleAge"),
years(interpretation, "YoungerPossibleAge"),
) {
return Some(ordered(older, younger));
}
if let Some(FieldValue::Object(time)) = interpretation.field("AbsoluteAge")
&& let Some(age) = years(time, "AgeOffsetAttribute")
{
return Some([age, age]);
}
None
}
fn feature_of(read: &SceneObject) -> Option<String> {
read.feature
.as_ref()
.map(|f| f.uuid.to_ascii_lowercase())
.filter(|u| !u.is_empty())
}
fn age_of_represented(representation: &dyn Reflect, source: &dyn ValueSource) -> Option<[f64; 2]> {
let held = scene::interpretation_of(representation, source)?;
age_of(held.as_reflect())
}
pub fn is_cut_type(qualified_type: &str) -> bool {
[
"TriangulatedSetRepresentation",
"Grid2dRepresentation",
"IjkGridRepresentation",
]
.iter()
.any(|kind| qualified_type.ends_with(kind))
}