Complete Region: create from a profile, plus tuple and int friendliness

RegionFromProfile reuses the REGION command's kernel calls; doc.solids.region
exposes it with layer and delete_source options. The lifecycle test builds
regions from real circle and polyline entities and covers refusals, a move,
both formats and undo. create_entity now accepts coordinate tuples for point
properties and every generated float setter accepts Python ints.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix RiestraandClaude Sonnet 5 committed 2026-09-20 17:37:10 +02:00
1 parent 04cd9e462d
commit c731d3a9a4
9 files changed
+233 -7

No files matched your search

+4
View File
@@ -406,6 +406,10 @@ fn leaf_to_py(registry: &TypeRegistry, type_id: &str, item_expr: &str) -> String
/// `value_expr` must already be an owned `PyObjectRef` expression. Result: a `PyResult<T>` expression.
fn leaf_from_py(registry: &TypeRegistry, type_id: &str, value_expr: &str) -> String {
if type_id == "f64" {
// RustPython's f64 conversion rejects ints; scripts write `radius=5`.
return format!("py_number_to_f64({value_expr}, vm)");
}
if type_id == "u64" {
// serde reports Rust `usize` as `u64`; the inferred cast serves both.
return format!("{value_expr}.try_into_value::<u64>(vm).map(|n| n as _)");
+30 -1
View File
@@ -1,5 +1,27 @@
"""Thin Python view over OCS host API v7 entity transactions and coverage."""
def _coordinates(value, width):
return dict(zip("xyz"[:width], (float(axis) for axis in value)))
def _coerce_points(kind, properties):
"""Let create_entity take (x, y, z) tuples for point properties, as
transaction edits already do, by using the property types in the schema."""
schema = ocs.entity_coverage(kind)
types = {row["name"]: row for row in schema["properties"]} if schema else {}
result = {}
for name, value in properties.items():
row = types.get(name)
if row is not None and row["type"] in ("Vector2", "Vector3"):
width = 2 if row["type"] == "Vector2" else 3
if row["sequence"]:
value = [_coordinates(item, width) if isinstance(item, (tuple, list)) else item for item in value]
elif isinstance(value, (tuple, list)):
value = _coordinates(value, width)
result[name] = value
return result
class _Entity:
def __init__(self, document, handle):
object.__setattr__(self, "_document", document)
@@ -117,6 +139,13 @@ class _Solids:
def pyramid(self, center=(0, 0, 0), radius=1, height=1, sides=4, layer=None):
return self._create("pyramid", tuple(center) + (radius, height, sides), layer)
def region(self, profile, layer=None, delete_source=False):
"""Make a planar region from one closed planar profile (a circle,
ellipse, closed polyline or spline). Keeps the profile unless
`delete_source` is true, and uses the profile's layer by default."""
handle = profile.handle if isinstance(profile, _Entity) else int(profile)
return self._document.entities[ocs.solid_region(handle, 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)
@@ -151,7 +180,7 @@ class _Document:
"""Create one mapped entity and return its live document descriptor."""
if "kind" in properties or "handle" in properties:
raise ValueError("kind and handle are managed by create_entity")
handle = ocs.add(dict(kind=kind, **properties))
handle = ocs.add(dict(kind=kind, **_coerce_points(kind, properties)))
return self.entities[handle]
def delete_entity(self, entity):
+17
View File
@@ -458,6 +458,23 @@ mod ocs {
.map_err(|error| vm.new_runtime_error(format!("ocs.solid_create: {error}")))
}
/// Build a planar region from a closed planar profile entity.
#[pyfunction]
fn solid_region(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::RegionFromProfile {
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_region: {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> {