Add kernel booleans: union, subtract and intersect for Solid3D

SolidOperation::Boolean runs synchronously, surfaces the kernel's refusal
with a plain reason and leaves the operands untouched, consumes them on
success unless keep_operands, and records one undo step. The lifecycle test
uses planar boxes with exact volumes and covers chaining, refusals, both
formats and undo; an ignored test shows a curved refusal reaching the script.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix RiestraandClaude Sonnet 5 committed 2026-09-20 17:58:38 +02:00
1 parent 69047549e7
commit 5a39052397
8 files changed
+320 -1

No files matched your search

@@ -146,6 +146,26 @@ class _Solids:
handle = profile.handle if isinstance(profile, _Entity) else int(profile)
return self._document.entities[ocs.solid_region(handle, layer, bool(delete_source))]
def _boolean(self, operation, first, second, layer, keep_operands):
a = first.handle if isinstance(first, _Entity) else int(first)
b = second.handle if isinstance(second, _Entity) else int(second)
return self._document.entities[ocs.solid_boolean(a, b, operation, layer, bool(keep_operands))]
def union(self, first, second, layer=None, keep_operands=False):
"""Join two solids into a new one. The operands are consumed unless
`keep_operands`. The kernel may refuse (the error says why) and a
refusal changes nothing. Curved operands can take a second or two,
and a whole script has a 30 second budget."""
return self._boolean("union", first, second, layer, keep_operands)
def subtract(self, first, second, layer=None, keep_operands=False):
"""Remove `second` from `first`, giving a new solid."""
return self._boolean("subtract", first, second, layer, keep_operands)
def intersect(self, first, second, layer=None, keep_operands=False):
"""Keep only the volume both solids share, as a new solid."""
return self._boolean("intersect", first, second, layer, keep_operands)
def surface(self, profile, layer=None, delete_source=False):
"""Make a plane surface from one closed planar profile."""
handle = profile.handle if isinstance(profile, _Entity) else int(profile)
+34
View File
@@ -475,6 +475,40 @@ mod ocs {
.map_err(|error| vm.new_runtime_error(format!("ocs.solid_region: {error}")))
}
/// Combine two solids ("union", "subtract" or "intersect").
#[pyfunction]
fn solid_boolean(
first: u64,
second: u64,
operation: String,
layer: Option<String>,
keep_operands: bool,
vm: &VirtualMachine,
) -> PyResult<u64> {
use ocs_plugin_api::host::{SolidBoolean, SolidOperation};
let operation = match operation.as_str() {
"union" => SolidBoolean::Union,
"subtract" => SolidBoolean::Subtract,
"intersect" => SolidBoolean::Intersect,
other => {
return Err(vm.new_value_error(format!("ocs.solid_boolean: unknown operation {other:?}")))
}
};
let result = host_ctx::with_host(|host| {
host.solid_operation(SolidOperation::Boolean {
first: Handle::new(first),
second: Handle::new(second),
operation,
layer,
keep_operands,
})
})
.ok_or_else(|| vm.new_runtime_error("ocs: not running inside a PY_ command".to_owned()))?;
result
.map(|handle| handle.value())
.map_err(|error| vm.new_runtime_error(format!("ocs.solid_boolean: {error}")))
}
/// Build a plane surface from a closed planar profile entity.
#[pyfunction]
fn solid_surface(source: u64, layer: Option<String>, delete_source: bool, vm: &VirtualMachine) -> PyResult<u64> {