// SPDX-License-Identifier: Apache-2.0
///|
fn number_or_null(value : Double) -> Json {
if not_finite(value) {
Json::null()
} else {
@json.to_json(value)
}
}
///|
fn vector_or_null(value : Vec3) -> Json {
if finite_vertex(value) {
Json([value.x, value.y, value.z])
} else {
Json::null()
}
}
///|
/// Version-1 machine report for coordinates interpreted in mm. 3MF parsers
/// already convert declared units; other formats require caller confirmation.
/// Non-finite computed quantities become null and force needs_review=true.
/// This does not certify self-intersection absence or printability.
///
/// ```moonbit nocheck
/// let report = @stlkit.inspection_json(mesh, format=@stlkit.Obj, samples=32)
/// ```
pub fn inspection_json(
mesh : Mesh,
format~ : MeshFormat = Unknown,
min_thickness~ : Double = 0.8,
samples~ : Int32 = 1000,
) -> Result[Json, String] {
if mesh.triangle_count() > 715827882 {
return Err("inspection face count overflows expanded corner count")
}
if not_finite(min_thickness) ||
min_thickness < 0.0 ||
samples < 1 ||
samples > 100000 {
return Err(
"inspection options: finite nonnegative thickness; samples 1..100000",
)
}
for triangle in mesh.triangles {
if !finite_vertex(triangle.normal) ||
!finite_vertex(triangle.a) ||
!finite_vertex(triangle.b) ||
!finite_vertex(triangle.c) {
return Err(
"inspection input contains non-finite coordinates or declared normals",
)
}
}
let review = check_printability(mesh, min_thickness, samples~)
let validation = review.validation
let area = mesh.surface_area()
let volume = mesh.volume()
let mass_ready = review.mass_properties_available()
let center = if mass_ready {
vector_or_null(mesh.center_mass())
} else {
Json::null()
}
let mut numeric_failure = not_finite(area) || not_finite(volume)
let mut mass_non_finite = not_finite(volume) || center is Null
let inertia : Json = if mass_ready {
let tensor = mesh.moment_inertia()
if tensor.any(not_finite) {
mass_non_finite = true
}
if !inertia_numeric_valid(tensor) {
numeric_failure = true
Json::null()
} else {
Json::array(tensor.iter().map(value => Json(value)).collect())
}
} else {
Json::null()
}
if mass_ready && center is Null {
numeric_failure = true
}
let mass_usable = mass_ready &&
!not_finite(volume) &&
volume > 0.0 &&
!(center is Null) &&
!(inertia is Null)
let bbox = match mesh.bounding_box() {
Some(box) =>
Json::object({
"min": vector_or_null(box.min),
"max": vector_or_null(box.max),
})
None => Json::null()
}
let thickness = match review.thickness {
Some(result) => {
let usable = result.hits > 0 &&
!not_finite(result.min_thickness) &&
!not_finite(result.mean_thickness)
if result.hits > 0 && !usable {
numeric_failure = true
}
Json::object({
"status": Json(
if result.hits == 0 {
"no_hits"
} else if usable {
"sample_estimate"
} else {
"non_finite"
},
),
"requested_samples": Json(samples),
"samples": Json(result.samples),
"hits": Json(result.hits),
"min_mm": if usable {
Json(result.min_thickness)
} else {
Json::null()
},
"mean_mm": if usable {
Json(result.mean_thickness)
} else {
Json::null()
},
"global_minimum_certified": Json(false),
})
}
None =>
Json::object({
"status": Json("not_evaluated"),
"requested_samples": Json(samples),
"samples": Json(0),
"hits": Json(0),
"min_mm": Json::null(),
"mean_mm": Json::null(),
"global_minimum_certified": Json(false),
})
}
Ok(
Json::object({
"schema_version": Json(1),
"ok": Json(true),
"input": Json::object({
"format": Json(format.id()),
"coordinate_units": Json("mm"),
"unit_interpretation": Json(
if format is ThreeMF {
"converted_from_3mf_unit"
} else {
"assumed_mm_confirm_scale"
},
),
}),
"geometry": Json::object({
"triangles": Json(validation.triangle_count),
"raw_vertices": Json(validation.raw_vertex_count),
"unique_vertices": Json(validation.unique_vertex_count),
"surface_area_mm2": number_or_null(area),
"volume_mm3": if mass_usable {
number_or_null(volume)
} else {
Json::null()
},
"aabb_mm": bbox,
}),
"validation": Json::object({
"boundary_edges": Json(validation.boundary_edge_count),
"non_manifold_edges": Json(validation.non_manifold_edge_count),
"degenerate_faces": Json(validation.degenerate_count),
"opposed_declared_normals": Json(validation.wrong_normal_count),
"euler_number": Json(validation.euler_number()),
"winding_consistent": Json(review.winding.consistent),
"flipped_faces": Json(review.winding.flipped_faces),
"inward_components": Json(review.inward_component_count()),
"orientation_reliable": Json(review.winding.orientation_reliable()),
}),
"mass_properties": Json::object({
"status": Json(
if !mass_ready {
"not_evaluated"
} else if !mass_usable {
if mass_non_finite {
"non_finite"
} else {
"not_evaluated"
}
} else {
"available"
},
),
"center_of_mass_mm": if mass_usable {
center
} else {
Json::null()
},
"inertia_density_one": if mass_usable {
inertia
} else {
Json::null()
},
}),
"thickness": thickness,
"review": Json::object({
"needs_review": Json(review.needs_review() || numeric_failure),
"numeric_failure": Json(numeric_failure),
"printability_certified": Json(false),
"min_thickness_mm": Json(min_thickness),
"scope": Json(review_scope()),
}),
}),
)
}