use std::collections::HashSet;
use crate::feature_pipeline::features::common;
use crate::feature_pipeline::{FeatureContext, FeatureResult};
use crate::{BooleanOperation, BooleanOptions, BrepSolid};
pub fn execute(ctx: &FeatureContext) -> FeatureResult {
match build(ctx) {
Ok(result) => result,
Err(error) => ctx.fail(error),
}
}
struct BooleanParam {
operation: String,
targets: Vec<String>,
merge_coplanar_faces: bool,
}
fn read_target_name(ctx: &FeatureContext) -> Option<String> {
match ctx.param("targetSolid")? {
serde_json::Value::Array(items) => items.iter().find_map(common::reference_name),
other => common::reference_name(other),
}
}
fn read_boolean_param(ctx: &FeatureContext) -> BooleanParam {
let boolean = ctx.param("boolean");
let operation = boolean
.and_then(|value| value.get("operation"))
.and_then(|value| value.as_str())
.unwrap_or("NONE")
.to_uppercase();
let targets = boolean
.and_then(|value| value.get("targets"))
.and_then(|value| value.as_array())
.map(|array| array.iter().filter_map(common::reference_name).collect())
.unwrap_or_default();
let merge_coplanar_faces = boolean
.and_then(|value| value.get("mergeCoplanarFaces"))
.and_then(|value| value.as_bool())
.unwrap_or(true);
BooleanParam {
operation,
targets,
merge_coplanar_faces,
}
}
fn fold_boolean(
base: BrepSolid,
targets: &[(String, u32)],
operation: BooleanOperation,
options: &BooleanOptions,
) -> Result<BrepSolid, String> {
let mut current = base;
for (name, handle) in targets {
let operand = crate::register_solid_value(current);
let folded = crate::with_two_registered_solids(*handle, operand, |target, running| {
crate::boolean_operation(target, running, operation, options)
});
crate::free_registered_solid(operand);
current = folded.map_err(|error| format!("boolean {operation:?} on '{name}' failed: {error}"))?;
}
Ok(current)
}
fn union_tools(tools: &[(String, u32)]) -> Result<BrepSolid, String> {
let options = BooleanOptions::default();
let mut current = crate::with_registered_solid_str(tools[0].1, |solid| Ok(solid.clone()))?;
for (name, handle) in &tools[1..] {
let running = crate::register_solid_value(current);
let folded = crate::with_two_registered_solids(running, *handle, |accumulated, tool| {
crate::boolean_operation(accumulated, tool, BooleanOperation::Union, &options)
});
crate::free_registered_solid(running);
current = folded.map_err(|error| format!("tool union at '{name}' failed: {error}"))?;
}
Ok(current)
}
fn build(ctx: &FeatureContext) -> Result<FeatureResult, String> {
let mut result = FeatureResult::empty(ctx.id.clone(), ctx.feature_type.clone());
let boolean = read_boolean_param(ctx);
if boolean.operation == "NONE" || boolean.targets.is_empty() {
return Ok(result);
}
let operation = common::parse_boolean_operation(&boolean.operation)?;
let target = match read_target_name(ctx) {
Some(name) => match ctx.scene.resolve_solid(&name) {
Some(handle) => Some((name, handle)),
None => {
result.unresolved.push(name);
None
}
},
None => None,
};
let mut tools: Vec<(String, u32)> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for name in &boolean.targets {
if !seen.insert(name.clone()) {
continue;
}
match ctx.scene.resolve_solid(name) {
Some(handle) => tools.push((name.clone(), handle)),
None => result.unresolved.push(name.clone()),
}
}
let Some((target_name, target_handle)) = target else {
return Ok(result);
};
if tools.is_empty() {
return Ok(result);
}
let options = BooleanOptions {
merge_coplanar_faces: boolean.merge_coplanar_faces,
..BooleanOptions::default()
};
let (solid, result_name, removed): (BrepSolid, String, Vec<String>) = match operation {
BooleanOperation::Subtract => {
let solid = if tools.len() == 1 {
crate::with_two_registered_solids(target_handle, tools[0].1, |target, tool| {
crate::boolean_operation(target, tool, BooleanOperation::Subtract, &options)
})
.map_err(|error| format!("boolean SUBTRACT failed: {error}"))?
} else {
let union = union_tools(&tools)?;
let union_handle = crate::register_solid_value(union);
let folded = crate::with_two_registered_solids(target_handle, union_handle, |target, tool_union| {
crate::boolean_operation(target, tool_union, BooleanOperation::Subtract, &options)
});
crate::free_registered_solid(union_handle);
folded.map_err(|error| format!("boolean SUBTRACT failed: {error}"))?
};
let mut removed: Vec<String> = tools.iter().map(|(name, _)| name.clone()).collect();
removed.push(target_name.clone());
(solid, target_name, removed)
}
_ => {
let base = crate::with_registered_solid_str(target_handle, |solid| Ok(solid.clone()))?;
let solid = fold_boolean(base, &tools, operation, &options)?;
let result_name = tools[0].0.clone();
let mut removed = vec![target_name];
removed.extend(tools.iter().map(|(name, _)| name.clone()));
(solid, result_name, removed)
}
};
result.added.push(common::register_added(solid, &result_name));
result.removed = removed;
Ok(result)
}
pub fn context_applicable(probe: &crate::feature_pipeline::SelectionProbe) -> bool {
probe.solids > 0
}
pub fn schema() -> serde_json::Value {
serde_json::json!({
"type": "B",
"shortName": "B",
"longName": "Boolean",
"displayBuilder": false,
"inputParamsSchema": {
"id": {
"type": "string",
"default_value": null,
"hint": "unique identifier for the boolean feature"
},
"targetSolid": {
"type": "reference_selection",
"selectionFilter": [
"SOLID"
],
"multiple": false,
"default_value": null,
"hint": "Primary target solid"
},
"boolean": {
"type": "boolean_operation",
"default_value": {
"targets": [],
"operation": "UNION",
"mergeCoplanarFaces": true
},
"hint": "Operation + other solids (as tools)"
}
}
})
}