forked from 1b-agent/opencadstudio-upstream
- CreateTable(TableSpec{insertion_point, data[row][col]}) op (append-only).
- backend add_table via Table::new(insertion_point, rows, cols) + set_cell_text
(Table::default() pre-creates 3x3, so use Table::new for exact dims).
- executor validates non-empty rectangular grid; facade create_table.
- spec Table constructor; api_reference + binding_schema regenerated; mocks + baseline.
- Test phase5ii_create_table_from_grid (3x2 grid, non-rectangular rejected)
-> 615 host lib + 3 consistency green. Phase 5 complete.
172 lines
9.0 KiB
Rust
172 lines
9.0 KiB
Rust
//! The thin host hook (plan §6, decision #11). The host implements this trait;
|
||
//! the crate's [`crate::executor`] drives it. Both the in-process transport and
|
||
//! the host's IPC executor call into it. Logic lives in the versioned crate; the
|
||
//! host only provides scene primitives.
|
||
|
||
use crate::error::{ApiError, ApiResult};
|
||
use crate::id::ObjectId;
|
||
use crate::ops::Curve2Spec;
|
||
use crate::query::{Aabb, EntityView};
|
||
use crate::revision::GeometryRevision;
|
||
|
||
/// A kernel B-rep solid body, opaque to the wire. On `host` this is
|
||
/// `cadkernel::brep::Body`; kernel keys never cross the API (decision #7).
|
||
pub type KernelBody = cadkernel::brep::Body;
|
||
|
||
/// The per-op host hook. Every method maps to an already-present scene primitive.
|
||
/// The executor calls `push_undo` BEFORE applying a write op and `finalize_op`
|
||
/// AFTER — recording exactly one undo step + one geometry bump per applied op.
|
||
pub trait DocApiBackend {
|
||
// ── identity / solid lookup ────────────────────────────────────────────
|
||
/// Resolve a live B-rep body for `id` (solid cache; re-lift from AcisData on
|
||
/// miss). `UnknownId` if `id` is not a live solid.
|
||
fn resolve_body(&mut self, id: ObjectId) -> ApiResult<KernelBody>;
|
||
|
||
/// Store a new solid body as a `Solid3D` entity; returns the fresh `ObjectId`.
|
||
/// Mirrors host `add_solid_model` (edge wires + `solid_to_sat` +
|
||
/// `set_sat_document` + `register_solid_model`).
|
||
fn store_solid(&mut self, body: &KernelBody) -> ApiResult<ObjectId>;
|
||
|
||
/// Replace the body of an existing solid in place (same `ObjectId`).
|
||
/// Re-embeds SAT and refreshes the solid cache.
|
||
fn update_solid(&mut self, id: ObjectId, body: &KernelBody) -> ApiResult<()>;
|
||
|
||
// ── plain (2D) entities ────────────────────────────────────────────────
|
||
/// 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>;
|
||
|
||
/// Add a paper-space `VIEWPORT`; returns the fresh `ObjectId`.
|
||
fn add_viewport(&mut self, spec: &crate::ops::ViewportSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Add a single-line `TEXT` annotation; returns the fresh `ObjectId`.
|
||
fn add_text(&mut self, spec: &crate::ops::TextSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Add a multi-line `MTEXT` annotation; returns the fresh `ObjectId`.
|
||
fn add_mtext(&mut self, spec: &crate::ops::MTextSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Set the text content of a Text/MText annotation in place.
|
||
fn set_text_content(&mut self, id: ObjectId, value: &str) -> ApiResult<()>;
|
||
|
||
/// The text content of a Text/MText annotation. `Unsupported` for other kinds.
|
||
fn text_content(&self, id: ObjectId) -> ApiResult<String>;
|
||
|
||
/// Add a `HATCH` (single closed polyline boundary); returns the fresh `ObjectId`.
|
||
fn add_hatch(&mut self, spec: &crate::ops::HatchSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// A hatch's boundary loops (outer + islands) as closed polylines.
|
||
fn hatch_boundary(&self, id: ObjectId) -> ApiResult<Vec<Vec<[f64; 2]>>>;
|
||
|
||
/// Add a linear `DIMENSION`; returns the fresh `ObjectId`.
|
||
fn add_dimension_linear(&mut self, spec: &crate::ops::DimensionSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Add a radial `DIMENSION`; returns the fresh `ObjectId`.
|
||
fn add_dimension_radius(&mut self, spec: &crate::ops::DimensionRadialSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Add a diameter `DIMENSION`; returns the fresh `ObjectId`.
|
||
fn add_dimension_diameter(&mut self, spec: &crate::ops::DimensionRadialSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Add a 3-point angular `DIMENSION`; returns the fresh `ObjectId`.
|
||
fn add_dimension_angular(&mut self, spec: &crate::ops::DimensionAngularSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Add an `ATTDEF` (in-block attribute definition); returns the fresh `ObjectId`.
|
||
fn add_attribute_definition(&mut self, spec: &crate::ops::AttributeDefinitionSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Add a `TABLE` (rows × columns grid); returns the fresh `ObjectId`.
|
||
fn add_table(&mut self, spec: &crate::ops::TableSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// A dimension's measured value (distance for linear/radius, degrees for angular).
|
||
fn dimension_measurement(&self, id: ObjectId) -> ApiResult<f64>;
|
||
|
||
/// Set an attribute `value` for `tag` on an insert (adds if absent). Insert-only.
|
||
fn set_attribute(&mut self, id: ObjectId, tag: &str, value: &str) -> ApiResult<()>;
|
||
|
||
/// An insert's attributes as (tag, value) pairs. Insert-only.
|
||
fn attributes(&self, id: ObjectId) -> ApiResult<Vec<(String, String)>>;
|
||
|
||
/// The entities inside a block definition (read-only traversal), as views.
|
||
fn block_entities(&self, block_name: &str) -> ApiResult<Vec<EntityView>>;
|
||
|
||
/// Set a viewport's view target + zoom height in place. Viewport-only.
|
||
fn set_viewport_view(&mut self, id: ObjectId, view_target: [f64; 3], view_height: f64) -> ApiResult<()>;
|
||
|
||
/// A viewport's view (target WCS + zoom height). Viewport-only.
|
||
fn viewport_view(&self, id: ObjectId) -> ApiResult<([f64; 3], f64)>;
|
||
|
||
/// Add a `RASTER_IMAGE` (host auto-registers the ImageDefinition).
|
||
fn add_raster_image(&mut self, spec: &crate::ops::RasterImageSpec) -> ApiResult<ObjectId>;
|
||
|
||
/// Loft a solid through >= 2 profile curve sets (each already resolved to
|
||
/// `geom2d::Curve`s on the XY plane). Returns the fresh `ObjectId`.
|
||
fn loft(&mut self, sections: &[Vec<cadkernel::geom2d::Curve>]) -> ApiResult<ObjectId>;
|
||
|
||
/// Can `id` be modified in place right now (exists, is the expected family,
|
||
/// not on a locked layer)? Read-only pre-check used before mutations.
|
||
/// Default: existence + not-locked (backends narrow the family check).
|
||
fn can_modify(&self, id: ObjectId) -> ApiResult<()> {
|
||
if self.entity_exists(id) {
|
||
Ok(())
|
||
} else {
|
||
Err(ApiError::UnknownId(id))
|
||
}
|
||
}
|
||
|
||
/// 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>;
|
||
|
||
/// Can `id` be removed right now (exists and is not blocked, e.g. by a locked
|
||
/// layer)? Read-only; used by the boolean-erase path to validate BEFORE any
|
||
/// mutation so a mid-op failure cannot leave partial state. Default: existence-only.
|
||
fn can_remove(&self, id: ObjectId) -> ApiResult<()> {
|
||
if self.entity_exists(id) {
|
||
Ok(())
|
||
} else {
|
||
Err(ApiError::UnknownId(id))
|
||
}
|
||
}
|
||
|
||
/// A generic, untyped view of any entity (id + kind + coarse bounds).
|
||
fn get_entity(&mut self, id: ObjectId) -> ApiResult<EntityView>;
|
||
|
||
/// The 2D curve geometry of a profile entity (polyline/closed curve) for
|
||
/// sweep ops (`Extrude`/`Revolve`). `UnknownId`/`Unsupported` if not a profile.
|
||
fn profile_curves(&self, id: ObjectId) -> ApiResult<Vec<cadkernel::geom2d::Curve>>;
|
||
|
||
/// Apply a rigid similarity to any entity in place (same `ObjectId`).
|
||
fn transform_entity(&mut self, id: ObjectId, placement: &crate::ops::PlacementSpec) -> ApiResult<()>;
|
||
|
||
/// Validate that `id` exists and can be transformed, WITHOUT mutating.
|
||
/// Used by `TransformMany` to pre-validate all ids before `push_undo` so the
|
||
/// apply loop cannot fail part-way (all-or-nothing, plan §5.3). Default:
|
||
/// existence-only (backends that restrict transform to a family override).
|
||
fn ensure_transformable(&mut self, id: ObjectId) -> ApiResult<()> {
|
||
if self.entity_exists(id) {
|
||
Ok(())
|
||
} else {
|
||
Err(ApiError::UnknownId(id))
|
||
}
|
||
}
|
||
|
||
/// Insert a vertex into a polyline at index `at` (polyline family only).
|
||
fn add_vertex(&mut self, id: ObjectId, at: usize, point: [f64; 3]) -> ApiResult<()>;
|
||
|
||
// ── queries (no undo, no bump; `&mut` because solid resolution may
|
||
// populate the kernel body cache on miss) ─────────────────────────────
|
||
fn bounds(&mut self, id: ObjectId) -> ApiResult<Aabb>;
|
||
fn centroid(&mut self, id: ObjectId) -> ApiResult<[f64; 3]>;
|
||
fn volume(&mut self, id: ObjectId) -> ApiResult<f64>;
|
||
fn entity_exists(&self, id: ObjectId) -> bool;
|
||
fn revision(&self) -> GeometryRevision;
|
||
|
||
// ── per-op boundary ────────────────────────────────────────────────────
|
||
/// Called BEFORE applying a write op (records the before-state undo snapshot).
|
||
fn push_undo(&mut self, label: &str);
|
||
/// Called AFTER a successful write op (one DeltaSnapshot + one geometry bump
|
||
/// + one publish). NOT called on failure (a failed op records nothing).
|
||
fn finalize_op(&mut self);
|
||
}
|