Files
OneCAD-Draft/crates/ocs_doc_api/src/backend.rs
T

314 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! The thin host hook. 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, LayerInfo};
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. NOTE: returns an OWNED body
/// (a clone of the cache entry); use `with_body` for read-only queries.
fn resolve_body(&mut self, id: ObjectId) -> ApiResult<KernelBody>;
/// Borrow the cached body read-only for a query, avoiding the `resolve_body`
/// deep clone. Default: falls back to `resolve_body` (clone). The host overrides
/// this to borrow the `solid_models` cache in place (O(1), no copy).
fn with_body<R>(
&mut self,
id: ObjectId,
f: &mut dyn FnMut(&KernelBody) -> ApiResult<R>,
) -> ApiResult<R> {
let body = self.resolve_body(id)?;
f(&body)
}
/// 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>;
/// Prepare every entity before recording one undo step and committing the batch.
fn create_many(&mut self, specs: &[crate::ops::EntitySpec]) -> ApiResult<Vec<ObjectId>>;
/// Prepare all transformed geometry before committing any entity.
fn transform_many(
&mut self,
ids: &[ObjectId],
placement: &crate::PlacementSpec,
) -> ApiResult<()>;
/// 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>;
/// Add a 2-line angular `DIMENSION`; returns the fresh `ObjectId`.
fn add_dimension_angular2ln(
&mut self,
spec: &crate::ops::DimensionAngularSpec,
) -> 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: &[(cadkernel::space::Plane, Vec<cadkernel::geom2d::Curve>)],
) -> ApiResult<ObjectId>;
/// Attach or replace an XDATA record for `application_name` on `id`.
/// `None` removes any existing record for that application.
fn set_xdata(
&mut self,
id: ObjectId,
application_name: &str,
record: Option<&crate::ops::XDataRecord>,
) -> ApiResult<()>;
/// Read the XDATA record for `application_name` on `id` (`None` if absent).
fn xdata(
&self,
id: ObjectId,
application_name: &str,
) -> ApiResult<Option<crate::ops::XDataRecord>>;
/// Create a standalone `XRECORD` object from a spec; returns the fresh `ObjectId`.
fn add_xrecord(&mut self, spec: &crate::ops::XRecordSpec) -> ApiResult<ObjectId>;
/// Replace an existing `XRECORD` object's payload in place.
fn set_xrecord(&mut self, id: ObjectId, spec: &crate::ops::XRecordSpec) -> ApiResult<()>;
/// Read an existing `XRECORD` object's payload (`None` if `id` is not an XRecord).
fn xrecord(&self, id: ObjectId) -> ApiResult<Option<crate::ops::XRecordSpec>>;
// ── layer CRUD ─────────────────────────────────────────────────────────
/// Create a new layer. Fails with `Validation` if the name already exists or is reserved.
fn create_layer(&mut self, info: &crate::ops::LayerInfo) -> ApiResult<()>;
/// Update an existing layer's properties by name. Fails with `Validation` if absent.
fn update_layer(&mut self, name: &str, info: &crate::ops::LayerInfo) -> ApiResult<()>;
/// Delete a layer by name. Fails with `Validation` if the layer is "0",
/// the current layer, does not exist, or has entities.
fn delete_layer(&mut self, name: &str) -> ApiResult<()>;
/// Look up a layer by raw name, normalizing it. Default implementation scans
/// `layers()`; backends with O(1) lookup should override.
fn lookup_layer(&self, name: &str) -> ApiResult<LayerInfo> {
let norm = crate::layer::normalize_layer_name(name);
if norm.is_empty() {
return Err(ApiError::validation("LookupLayer", "empty layer name"));
}
self.layers()?
.into_iter()
.find(|l| l.normalized_name() == norm)
.ok_or_else(|| {
ApiError::validation(
"LookupLayer",
format!("layer '{name}' does not exist"),
)
})
}
/// Move an entity to a different layer. The target layer must exist.
fn set_entity_layer(&mut self, id: ObjectId, layer: &str) -> ApiResult<()>;
/// List all layers in the document.
fn layers(&self) -> ApiResult<Vec<crate::ops::LayerInfo>>;
/// The layer name of an entity.
fn entity_layer(&self, id: ObjectId) -> ApiResult<String>;
/// Enumerate first-class entities, optionally filtered by kind and/or layer.
/// `include_bounds` controls whether `EntityView::bounds` is populated; skip it
/// for lightweight list views.
fn enumerate_entities(
&self,
kind: Option<&str>,
layer: Option<&str>,
include_bounds: bool,
) -> ApiResult<Vec<EntityView>>;
/// The WCS location of a Point entity.
fn point_position(&self, id: ObjectId) -> ApiResult<[f64; 3]>;
/// The start and end WCS points of a Line entity.
fn line_geometry(&self, id: ObjectId) -> ApiResult<([f64; 3], [f64; 3])>;
/// 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))
}
}
/// Whether an object (named-object / XRecord / dictionary) exists. Distinct
/// from `entity_exists`, which is for first-class entities only.
fn object_exists(&self, id: ObjectId) -> bool;
/// 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>>;
fn profile_plane(&self, _id: ObjectId) -> ApiResult<cadkernel::space::Plane> {
Ok(cadkernel::space::Plane::XY)
}
/// 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, the bulk contract). 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);
/// Discard an undo capture after preparation or validation failed.
fn cancel_op(&mut self) {}
}