#![allow(clippy::print_stdout, clippy::expect_used, clippy::unwrap_used)]
use std::collections::HashMap;
use std::fmt::Write as _;
use std::path::PathBuf;
use brepkit_io::arena_io::deserialize_solid;
use brepkit_topology::Topology;
use brepkit_topology::edge::EdgeId;
use brepkit_topology::explorer::solid_faces;
use brepkit_topology::solid::SolidId;
fn describe(topo: &Topology, sid: SolidId, label: &str) {
let Ok(faces) = solid_faces(topo, sid) else {
println!(" {label}: <no faces>");
return;
};
let mut uses: HashMap<EdgeId, usize> = HashMap::new();
let mut users: HashMap<EdgeId, Vec<brepkit_topology::face::FaceId>> = HashMap::new();
let mut mix: HashMap<&'static str, usize> = HashMap::new();
for &fid in &faces {
let Ok(face) = topo.face(fid) else { continue };
*mix.entry(face.surface().type_tag()).or_default() += 1;
for wid in std::iter::once(face.outer_wire()).chain(face.inner_wires().iter().copied()) {
let Ok(w) = topo.wire(wid) else { continue };
for oe in w.edges() {
*uses.entry(oe.edge()).or_default() += 1;
users.entry(oe.edge()).or_default().push(fid);
}
}
}
let free = uses.values().filter(|&&c| c == 1).count();
let over = uses.values().filter(|&&c| c > 2).count();
if std::env::var("FREE_EDGES").is_ok() {
for (eid, n) in &uses {
if *n != 2
&& let Ok(e) = topo.edge(*eid)
&& let (Ok(a), Ok(b)) = (topo.vertex(e.start()), topo.vertex(e.end()))
{
let (a, b) = (a.point(), b.point());
let owners: Vec<String> = users
.get(eid)
.into_iter()
.flatten()
.map(|f| {
let tag = topo
.face(*f)
.map(|fc| fc.surface().type_tag())
.unwrap_or("?");
format!("{f:?}:{tag}")
})
.collect();
println!(
" {} edge {eid:?} {} ({:.3},{:.3},{:.3})->({:.3},{:.3},{:.3}) used_by={owners:?}",
if *n == 1 { "FREE" } else { "OVER" },
e.curve().type_tag(),
a.x(),
a.y(),
a.z(),
b.x(),
b.y(),
b.z()
);
if std::env::var("FREE_EDGES").is_ok_and(|v| v == "2") {
for f in users.get(eid).into_iter().flatten() {
let Ok(fc) = topo.face(*f) else { continue };
let mut lo = [f64::MAX; 3];
let mut hi = [f64::MIN; 3];
let mut nedges = 0;
for wid in
std::iter::once(fc.outer_wire()).chain(fc.inner_wires().iter().copied())
{
let Ok(w) = topo.wire(wid) else { continue };
for oe in w.edges() {
nedges += 1;
let Ok(e2) = topo.edge(oe.edge()) else {
continue;
};
for vid in [e2.start(), e2.end()] {
if let Ok(v) = topo.vertex(vid) {
let p = v.point();
for (i, c) in [p.x(), p.y(), p.z()].iter().enumerate() {
lo[i] = lo[i].min(*c);
hi[i] = hi[i].max(*c);
}
}
}
}
}
println!(
" owner {f:?} {} rev={} inner={} edges={nedges} bbox=({:.3},{:.3},{:.3})..({:.3},{:.3},{:.3})",
fc.surface().type_tag(),
fc.is_reversed(),
fc.inner_wires().len(),
lo[0],
lo[1],
lo[2],
hi[0],
hi[1],
hi[2]
);
}
}
}
}
}
let mut mix: Vec<_> = mix.into_iter().collect();
mix.sort_unstable();
let vol =
brepkit_operations::measure::oriented_solid_volume(topo, sid, 0.05).unwrap_or(f64::NAN);
let tess = if std::env::var("TESS_BND").is_ok() {
match brepkit_operations::tessellate::tessellate_solid_with_tolerance(
topo,
sid,
0.01,
5.0_f64.to_radians(),
) {
Ok(mesh) => format!(
" tess_bnd={} tess_nm={}",
brepkit_operations::tessellate::boundary_edge_count(&mesh),
brepkit_operations::tessellate::non_manifold_edge_count(&mesh)
),
Err(e) => format!(" tess_err={e}"),
}
} else {
String::new()
};
println!(
" {label}: F={} mix={mix:?} free={free} over={over} vol={vol:.3}{tess}",
faces.len()
);
}
struct Tap;
impl log::Log for Tap {
fn enabled(&self, m: &log::Metadata) -> bool {
let max = if std::env::var("BK_TRACE").is_ok() {
log::Level::Trace
} else {
log::Level::Debug
};
m.target().starts_with("brepkit_") && m.level() <= max
}
fn log(&self, r: &log::Record) {
if self.enabled(r.metadata()) {
println!(" [log] {}", r.args());
}
}
fn flush(&self) {}
}
static TAP: Tap = Tap;
fn main() {
let _ = log::set_logger(&TAP);
log::set_max_level(if std::env::var("BK_TRACE").is_ok() {
log::LevelFilter::Trace
} else {
log::LevelFilter::Debug
});
let a_path = PathBuf::from(std::env::var_os("A").expect("A=<path>"));
let b_path = PathBuf::from(std::env::var_os("B").expect("B=<path>"));
let op = std::env::var("OP").unwrap_or_else(|_| "fuse".to_string());
let mut topo = Topology::new();
let a = deserialize_solid(&std::fs::read(&a_path).unwrap(), &mut topo).unwrap();
let b = deserialize_solid(&std::fs::read(&b_path).unwrap(), &mut topo).unwrap();
describe(&topo, a, "A");
describe(&topo, b, "B");
let bop = match op.as_str() {
"cut" => brepkit_algo::bop::BooleanOp::Cut,
"intersect" => brepkit_algo::bop::BooleanOp::Intersect,
_ => brepkit_algo::bop::BooleanOp::Fuse,
};
if let Ok(spec) = std::env::var("POINT_IN") {
let defl: f64 = std::env::var("POINT_DEFL")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(0.01);
for (i, one) in spec.split(';').filter(|t| !t.trim().is_empty()).enumerate() {
let c: Vec<f64> = one
.split(',')
.filter_map(|t| t.trim().parse().ok())
.collect();
if c.len() != 3 {
continue;
}
let p = brepkit_math::vec::Point3::new(c[0], c[1], c[2]);
let mut row = format!(" POINT_IN[{i}] ({:.3},{:.3},{:.3})", c[0], c[1], c[2]);
for (label, sid) in [("A", a), ("B", b)] {
match brepkit_operations::classify::classify_point(&topo, sid, p, defl, 1e-7) {
Ok(v) => {
let _ = write!(row, " {label}={v:?}");
}
Err(_) => {
let _ = write!(row, " {label}=ERR");
}
}
}
println!("{row}");
}
return;
}
if let Ok(list) = std::env::var("TOOLS") {
let tools: Vec<_> = list
.split(',')
.filter(|t| !t.trim().is_empty())
.map(|t| deserialize_solid(&std::fs::read(t.trim()).unwrap(), &mut topo).unwrap())
.collect();
if std::env::var("TOOLS_SEQ").is_ok() {
println!("-- sequential cuts with {} tools --", tools.len());
let t = std::time::Instant::now();
let mut cur = a;
let mut failed = false;
for (i, &tool) in tools.iter().enumerate() {
match brepkit_operations::boolean::boolean(
&mut topo,
brepkit_operations::boolean::BooleanOp::Cut,
cur,
tool,
) {
Ok(next) => cur = next,
Err(e) => {
println!(" cut {i} FAILED: {e}");
failed = true;
break;
}
}
}
if !failed {
describe(
&topo,
cur,
&format!("sequential {}ms", t.elapsed().as_millis()),
);
}
return;
}
println!("-- compound_cut with {} tools --", tools.len());
let t = std::time::Instant::now();
match brepkit_operations::boolean::compound_cut(
&mut topo,
a,
&tools,
brepkit_operations::boolean::BooleanOptions::default(),
) {
Ok(sid) => {
describe(
&topo,
sid,
&format!("compound_cut {}ms", t.elapsed().as_millis()),
);
if let Ok(cpath) = std::env::var("CORNER") {
let ctool =
deserialize_solid(&std::fs::read(&cpath).unwrap(), &mut topo).unwrap();
let t2 = std::time::Instant::now();
match brepkit_operations::boolean::boolean(
&mut topo,
brepkit_operations::boolean::BooleanOp::Intersect,
sid,
ctool,
) {
Ok(rid) => describe(
&topo,
rid,
&format!("corner intersect {}ms", t2.elapsed().as_millis()),
),
Err(e) => println!(
" corner intersect FAILED in {}ms: {e}",
t2.elapsed().as_millis()
),
}
}
}
Err(e) => println!(
" compound_cut FAILED in {}ms: {e}",
t.elapsed().as_millis()
),
}
return;
}
println!("-- operations::boolean {op} --");
let ops_op = match op.as_str() {
"cut" => brepkit_operations::boolean::BooleanOp::Cut,
"intersect" => brepkit_operations::boolean::BooleanOp::Intersect,
_ => brepkit_operations::boolean::BooleanOp::Fuse,
};
let before = brepkit_operations::boolean::mesh_fallback_count();
let t = std::time::Instant::now();
match brepkit_operations::boolean::boolean(&mut topo, ops_op, a, b) {
Ok(sid) => {
let ms = t.elapsed().as_millis();
let fell_back = brepkit_operations::boolean::mesh_fallback_count() > before;
describe(
&topo,
sid,
&format!(
"OPS {op} {ms}ms{}",
if fell_back { " [MESH FALLBACK]" } else { "" }
),
);
}
Err(e) => println!(" OPS {op} FAILED in {}ms: {e}", t.elapsed().as_millis()),
}
println!("-- raw GFA {op} --");
let t = std::time::Instant::now();
match brepkit_algo::gfa::boolean(&mut topo, bop, a, b) {
Ok(sid) => {
describe(
&topo,
sid,
&format!("RAW {op} {}ms", t.elapsed().as_millis()),
);
if let Ok(out) = std::env::var("OUT") {
match brepkit_io::arena_io::serialize_solid(&topo, sid) {
Ok(bytes) => {
std::fs::write(&out, bytes).unwrap();
println!(" wrote raw result to {out}");
}
Err(e) => println!(" serialize failed: {e}"),
}
}
}
Err(e) => println!(" RAW {op} FAILED in {}ms: {e}", t.elapsed().as_millis()),
}
}