Complete Surface: plane surfaces, extrusions, isolines and moves

Adds SurfaceFromProfile and Extrude kernel operations (doc.solids.surface and
doc.solids.extrude), accepts surfaces in Transform, and maps Surface with
editable isolines. Fixes a real bug found by the test: the surface kind field
overwrote the entity-type kind key in Python dicts, so it is exposed as
surface_kind, with a catalog test guarding against name collisions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix RiestraandClaude Sonnet 5 committed 2026-09-20 17:47:22 +02:00
1 parent 918351f264
commit 9cd6653a58
14 files changed
+441 -34

No files matched your search

+1 -1
View File
@@ -94,7 +94,7 @@ The host-owned catalog classifies all 48 entity variants and tracks every
traced field, including fields marked `unmapped`. Query it with
`doc.coverage()` or `doc.coverage("Line")`; each row includes source field,
type, access, and current validation status. Geometry edits remain limited to
the 41 mapped kinds.
the 42 mapped kinds.
For any entity, `entity.snapshot` returns a detached dictionary of its raw
CAD fields, including `snapshot["common"]` and kind-specific nested values.
It can inspect an unmapped kind; editing the dictionary does
+36 -1
View File
@@ -40,7 +40,8 @@
"Ole2Frame",
"Solid3D",
"Body",
"Region"
"Region",
"Surface"
],
"manual_kinds": [
"Dimension"
@@ -891,6 +892,40 @@
"read_only": true
}
}
},
"Surface": {
"update_only": true,
"update_only_message": "Surface is built by the geometry kernel: use doc.solids.surface(profile) for a plane surface or doc.solids.extrude(open_profile, direction) for an extruded one; a script never writes the ACIS payload",
"fields": {
"acis_data": {
"exclude": true,
"_comment": "Opaque ACIS payload; never rewritten by a script."
},
"surface_data": {
"exclude": true,
"_comment": "Kind-specific sweep/loft parameters and embedded profiles; preserved untouched."
},
"wires": {
"exclude": true
},
"silhouettes": {
"exclude": true
},
"kind": {
"read_only": true,
"python_name": "surface_kind",
"_comment": "Renamed: a bare `kind` key names the entity type in every Python dict."
},
"modeler_format_version": {
"read_only": true
},
"point_of_reference": {
"read_only": true
},
"history_handle": {
"read_only": true
}
}
}
}
}
@@ -146,6 +146,18 @@ 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 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)
return self._document.entities[ocs.solid_surface(handle, layer, bool(delete_source))]
def extrude(self, profile, direction, layer=None, delete_source=False):
"""Extrude a planar profile along `direction`: a Solid3D when the
profile is closed, a Surface when it is open."""
handle = profile.handle if isinstance(profile, _Entity) else int(profile)
vector = [float(v) for v in direction]
return self._document.entities[ocs.solid_extrude(handle, vector, layer, bool(delete_source))]
def transform(self, entity, matrix):
"""Apply a column-major 4x4 rigid transform (16 numbers) in place."""
handle = entity.handle if isinstance(entity, _Entity) else int(entity)
+45 -1
View File
@@ -475,6 +475,50 @@ mod ocs {
.map_err(|error| vm.new_runtime_error(format!("ocs.solid_region: {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> {
use ocs_plugin_api::host::SolidOperation;
let result = host_ctx::with_host(|host| {
host.solid_operation(SolidOperation::SurfaceFromProfile {
source: Handle::new(source),
layer,
delete_source,
})
})
.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_surface: {error}")))
}
/// Extrude a planar profile: a solid if closed, a surface if open.
#[pyfunction]
fn solid_extrude(
source: u64,
direction: Vec<f64>,
layer: Option<String>,
delete_source: bool,
vm: &VirtualMachine,
) -> PyResult<u64> {
use ocs_plugin_api::host::SolidOperation;
let direction: [f64; 3] = direction.try_into().map_err(|_| {
vm.new_value_error("ocs.solid_extrude: the direction needs 3 numbers".to_owned())
})?;
let result = host_ctx::with_host(|host| {
host.solid_operation(SolidOperation::Extrude {
source: Handle::new(source),
direction,
layer,
delete_source,
})
})
.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_extrude: {error}")))
}
/// Apply a column-major 4x4 rigid transform to a solid, body or region.
#[pyfunction]
fn solid_transform(handle: u64, matrix: Vec<f64>, vm: &VirtualMachine) -> PyResult<u64> {
@@ -4905,7 +4949,7 @@ mod tests {
);
}
}
assert_eq!(emitted.len(), 41);
assert_eq!(emitted.len(), 42);
}
#[cfg(feature = "experimental-host-model")]