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 <[email protected]>
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

+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> {