ocs_doc_api: Phase 3 (block references)

- Operation::CreateInsert(InsertSpec) appended to the wire enum (append-only).
- backend add_insert validates the BlockRecord exists (unknown block -> Validation,
  no mutation); host inserts acadrust::entities::Insert with scale+rotation.
- Insert transform moves insert_point (transform_entity_geometry) +
  ensure_transformable includes Insert.
- facade EntityCollection::create_insert; spec Insert family; api_reference +
  binding_schema regenerated; mocks updated.
- Test phase3_create_insert_and_transform -> 599 host lib green.
- IMPLEMENTATION.md: Phase 3 done (attributes/traversal remaining).
This commit is contained in:
Sebastian committed 2026-09-06 01:05:51 +02:00
1 parent d46351cdb3
commit b03f68737e
13 files changed
+146 -5

No files matched your search

+4
View File
@@ -35,6 +35,10 @@ pub trait DocApiBackend {
/// Add a 2D curve entity; returns the fresh `ObjectId`.
fn add_curve(&mut self, spec: &Curve2Spec) -> ApiResult<ObjectId>;
/// Add a block reference (`INSERT`). Validates `block_name` exists; returns
/// the fresh `ObjectId`. `Validation` if the block is unknown.
fn add_insert(&mut self, spec: &crate::ops::InsertSpec) -> ApiResult<ObjectId>;
/// Remove any first-level entity by id. Returns `false` if absent; returns
/// `Err` if the entity exists but cannot be removed (e.g. locked layer).
fn remove_entity(&mut self, id: ObjectId) -> ApiResult<bool>;
+7
View File
@@ -176,6 +176,13 @@ pub fn apply_op<B: DocApiBackend>(b: &mut B, op: Operation) -> ApiResult<Receipt
b.finalize_op();
OpOutcome::Deleted(ids.clone())
}
Operation::CreateInsert(spec) => {
// Validate the block name exists before committing (no rollback needed).
b.push_undo(name);
let id = b.add_insert(spec)?;
b.finalize_op();
OpOutcome::NewId(id)
}
};
Ok(Receipt {
outcome: Some(outcome),
+15
View File
@@ -464,6 +464,21 @@ pub struct EntityCollection {
}
impl EntityCollection {
/// Place a block reference (`INSERT`) for `block_name` (must exist).
pub fn create_insert(&self, block_name: &str, insert_point: [f64; 3], scale: f64, rotation: f64) -> ApiResult<Entity> {
let receipt = self.session.apply_op(Operation::CreateInsert(crate::ops::InsertSpec {
block_name: block_name.to_string(),
insert_point,
scale,
rotation,
}))?;
let id = receipt
.outcome
.and_then(|o| o.new_id())
.ok_or_else(|| ApiError::Transport("create_insert returned no id".into()))?;
Ok(Entity::new(self.session.clone(), id))
}
/// Generic lookup by id (any family) → `Entity` (downcast via `as_solid()`, …).
pub fn get(&self, id: ObjectId) -> ApiResult<Entity> {
// Validate existence eagerly so `get` errors surface as `UnknownId`.
@@ -87,6 +87,14 @@ B-rep solid. Host resolves bodies via the solid_models cache (lift-on-miss from
- **create_xline**(`origin: [f64; 3], direction: [f64; 3]`) -> `XLine`
## `Entity` (acadrust `Insert`, collection `entities`)
Block reference. Place an existing BlockRecord by name; transform moves its insert point.
### Constructors (`doc.entities()`)
- **create_insert**(`block_name: &str, insert_point: [f64; 3], scale: f64, rotation: f64`) -> `Entity`
## Generic methods (every handle)
Every typed handle (`Solid`, `Line`, `Circle`, `Polyline`, `Point`, `Entity`) is a
@@ -187,6 +187,13 @@
"handle": "XLine",
"methods": [],
"name": "XLine"
},
{
"acadrust_variant": "Insert",
"collection": "entities",
"handle": "Entity",
"methods": [],
"name": "Insert"
}
],
"layouts": {
+3 -1
View File
@@ -6,7 +6,7 @@
use serde::{Deserialize, Serialize};
use crate::id::ObjectId;
use crate::ops::{BoolOp, Curve2Spec, EntitySpec, PlacementSpec, SolidPrimitive};
use crate::ops::{BoolOp, Curve2Spec, EntitySpec, InsertSpec, PlacementSpec, SolidPrimitive};
/// A typed write operation (plan §5). Each variant is ONE atomic host call = one
/// undo step. Append new variants at the END only.
@@ -27,4 +27,6 @@ pub enum Operation {
CreateMany(Vec<EntitySpec>),
TransformMany { ids: Vec<ObjectId>, placement: PlacementSpec },
DeleteMany(Vec<ObjectId>),
// containers
CreateInsert(InsertSpec),
}
+12
View File
@@ -56,6 +56,17 @@ pub enum EntitySpec {
Solid(SolidPrimitive),
}
/// Construction spec for a block reference (`INSERT`): place the block
/// `block_name` (must exist as a BlockRecord) at `insert_point` with uniform
/// `scale` and `rotation` (radians, about Z).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InsertSpec {
pub block_name: String,
pub insert_point: [f64; 3],
pub scale: f64,
pub rotation: f64,
}
/// A rigid similarity (rotation + translation + optional uniform scale/reflection)
/// as a plain-data mirror of `cadkernel::brep::transform`'s `Placement`
/// (non-serde). Columns are the new basis axes; `origin` is the translation.
@@ -103,6 +114,7 @@ impl crate::gen::Operation {
CreateMany(_) => "CreateMany",
TransformMany { .. } => "TransformMany",
DeleteMany(_) => "DeleteMany",
CreateInsert(_) => "CreateInsert",
}
}
}