forked from 1b-agent/opencadstudio-upstream
ocs_doc_api: Phase 2c-ii — dimension sub-types (radius/diameter/angular)
- CreateDimensionRadius/CreateDimensionDiameter (DimensionRadialSpec) + CreateDimensionAngular (DimensionAngularSpec) ops (append-only). - backend add_dimension_radius/diameter (DimensionRadius/DimensionDiameter::new) + add_dimension_angular (DimensionAngular3Pt::new computes angle); dimension_measurement returns degrees for angular sub-types. - facade create_dimension_radius/diameter/angular; spec constructors; api_reference + binding_schema regenerated; mocks + baseline. - Test phase2cii_dimension_radius_diameter_angular -> 613 host lib + 3 consistency green.
This commit is contained in:
1 parent
2522979285
commit
8d8c120880
12 files changed
+205
-3
No files matched your search
@@ -70,8 +70,11 @@ Phase 2b (annotations/hatch/dimension typed ops), attributes + nested traversal
|
||||
boundary edges, delete. Covered by `phase2b_hatch_*` test.
|
||||
- **Done (2b-c):** linear `Dimension` — `create_dimension_linear(first, second,
|
||||
definition)` (`CreateDimensionLinear`), `measurement()` → distance
|
||||
(`GetDimensionMeasurement`), bounds. Covered by `phase2c_*` test. Radius/diameter/
|
||||
angular dimension sub-types are a later refinement.
|
||||
(`GetDimensionMeasurement`), bounds. Covered by `phase2c_*` test.
|
||||
- **Done (2c-ii, sub-types):** `create_dimension_radius` / `create_dimension_diameter`
|
||||
(radial chord) and `create_dimension_angular` (vertex + 2 legs) → radial/degrees
|
||||
measurements. Covered by `phase2cii_*` test. (`Angular2Ln` variant handled in
|
||||
`measurement`; a dedicated constructor is a later refinement.)
|
||||
- **Unblocked:** `add_vertex` (polyline) and sweep profiles (Line/Circle/Arc/LwPolyline)
|
||||
are supported (Phase 0).
|
||||
|
||||
|
||||
@@ -183,6 +183,20 @@ impl DocApiBackend for MockBackend {
|
||||
self.dimension_measurements.insert(id, d);
|
||||
Ok(id)
|
||||
}
|
||||
fn add_dimension_radius(&mut self, spec: &ocs_doc_api::ops::DimensionRadialSpec) -> ApiResult<ObjectId> {
|
||||
let id = self.alloc("Dimension");
|
||||
let d = ((spec.center[0] - spec.point[0]).powi(2) + (spec.center[1] - spec.point[1]).powi(2) + (spec.center[2] - spec.point[2]).powi(2)).sqrt();
|
||||
self.dimension_measurements.insert(id, d);
|
||||
Ok(id)
|
||||
}
|
||||
fn add_dimension_diameter(&mut self, spec: &ocs_doc_api::ops::DimensionRadialSpec) -> ApiResult<ObjectId> {
|
||||
self.add_dimension_radius(spec)
|
||||
}
|
||||
fn add_dimension_angular(&mut self, _spec: &ocs_doc_api::ops::DimensionAngularSpec) -> ApiResult<ObjectId> {
|
||||
let id = self.alloc("Dimension");
|
||||
self.dimension_measurements.insert(id, 90.0);
|
||||
Ok(id)
|
||||
}
|
||||
fn dimension_measurement(&self, id: ObjectId) -> ApiResult<f64> {
|
||||
self.dimension_measurements.get(&id).copied().ok_or(ApiError::UnknownId(id))
|
||||
}
|
||||
|
||||
@@ -331,6 +331,21 @@ name = "create_dimension_linear"
|
||||
args = [{ name = "first_point", ty = "[f64; 3]" }, { name = "second_point", ty = "[f64; 3]" }, { name = "definition_point", ty = "[f64; 3]" }]
|
||||
op = "CreateDimensionLinear"
|
||||
|
||||
[[family.constructor]]
|
||||
name = "create_dimension_radius"
|
||||
args = [{ name = "center", ty = "[f64; 3]" }, { name = "point", ty = "[f64; 3]" }]
|
||||
op = "CreateDimensionRadius"
|
||||
|
||||
[[family.constructor]]
|
||||
name = "create_dimension_diameter"
|
||||
args = [{ name = "center", ty = "[f64; 3]" }, { name = "point", ty = "[f64; 3]" }]
|
||||
op = "CreateDimensionDiameter"
|
||||
|
||||
[[family.constructor]]
|
||||
name = "create_dimension_angular"
|
||||
args = [{ name = "vertex", ty = "[f64; 3]" }, { name = "first_point", ty = "[f64; 3]" }, { name = "second_point", ty = "[f64; 3]" }, { name = "arc_location", ty = "[f64; 3]" }]
|
||||
op = "CreateDimensionAngular"
|
||||
|
||||
[[family.method]]
|
||||
name = "measurement"
|
||||
kind = "query"
|
||||
|
||||
@@ -63,6 +63,15 @@ pub trait DocApiBackend {
|
||||
/// 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>;
|
||||
|
||||
/// A dimension's measured value (distance for linear/radius, degrees for angular).
|
||||
fn dimension_measurement(&self, id: ObjectId) -> ApiResult<f64>;
|
||||
|
||||
|
||||
@@ -269,6 +269,24 @@ pub fn apply_op<B: DocApiBackend>(b: &mut B, op: Operation) -> ApiResult<Receipt
|
||||
b.finalize_op();
|
||||
OpOutcome::NewId(id)
|
||||
}
|
||||
Operation::CreateDimensionRadius(spec) => {
|
||||
b.push_undo(name);
|
||||
let id = b.add_dimension_radius(spec)?;
|
||||
b.finalize_op();
|
||||
OpOutcome::NewId(id)
|
||||
}
|
||||
Operation::CreateDimensionDiameter(spec) => {
|
||||
b.push_undo(name);
|
||||
let id = b.add_dimension_diameter(spec)?;
|
||||
b.finalize_op();
|
||||
OpOutcome::NewId(id)
|
||||
}
|
||||
Operation::CreateDimensionAngular(spec) => {
|
||||
b.push_undo(name);
|
||||
let id = b.add_dimension_angular(spec)?;
|
||||
b.finalize_op();
|
||||
OpOutcome::NewId(id)
|
||||
}
|
||||
};
|
||||
Ok(Receipt {
|
||||
outcome: Some(outcome),
|
||||
|
||||
@@ -539,6 +539,32 @@ impl CurveCollection {
|
||||
Ok(Entity::new(self.session.clone(), id))
|
||||
}
|
||||
|
||||
/// A radial DIMENSION for the circle centered at `center` through `point`.
|
||||
pub fn create_dimension_radius(&self, center: [f64; 3], point: [f64; 3]) -> ApiResult<Dimension> {
|
||||
self.create_dim_radial(Operation::CreateDimensionRadius, center, point)
|
||||
}
|
||||
/// A diameter DIMENSION for the circle with chord points `center`/`point`.
|
||||
pub fn create_dimension_diameter(&self, center: [f64; 3], point: [f64; 3]) -> ApiResult<Dimension> {
|
||||
self.create_dim_radial(Operation::CreateDimensionDiameter, center, point)
|
||||
}
|
||||
fn create_dim_radial(
|
||||
&self,
|
||||
op: impl Fn(crate::ops::DimensionRadialSpec) -> Operation,
|
||||
center: [f64; 3],
|
||||
point: [f64; 3],
|
||||
) -> ApiResult<Dimension> {
|
||||
let receipt = self.session.apply_op(op(crate::ops::DimensionRadialSpec { center, point }))?;
|
||||
let id = receipt.outcome.and_then(|o| o.new_id()).ok_or_else(|| ApiError::Transport("create_dimension returned no id".into()))?;
|
||||
Ok(Dimension::new(self.session.clone(), id))
|
||||
}
|
||||
/// A 3-point angular DIMENSION (vertex + one point on each leg).
|
||||
pub fn create_dimension_angular(&self, vertex: [f64; 3], first_point: [f64; 3], second_point: [f64; 3], arc_location: [f64; 3]) -> ApiResult<Dimension> {
|
||||
let receipt = self.session.apply_op(Operation::CreateDimensionAngular(crate::ops::DimensionAngularSpec {
|
||||
vertex, first_point, second_point, arc_location,
|
||||
}))?;
|
||||
let id = receipt.outcome.and_then(|o| o.new_id()).ok_or_else(|| ApiError::Transport("create_dimension returned no id".into()))?;
|
||||
Ok(Dimension::new(self.session.clone(), id))
|
||||
}
|
||||
/// A linear DIMENSION between `first_point` and `second_point`, with the
|
||||
/// dimension line placed at `definition_point`. `measurement()` reads the value.
|
||||
pub fn create_dimension_linear(&self, first_point: [f64; 3], second_point: [f64; 3], definition_point: [f64; 3]) -> ApiResult<Dimension> {
|
||||
|
||||
@@ -133,6 +133,9 @@ Linear dimension between two measured points. measurement() reads the distance;
|
||||
### Constructors (`doc.curves()`)
|
||||
|
||||
- **create_dimension_linear**(first_point: [f64; 3], second_point: [f64; 3], definition_point: [f64; 3]) -> `Dimension` — `CreateDimensionLinear`
|
||||
- **create_dimension_radius**(center: [f64; 3], point: [f64; 3]) -> `Dimension` — `CreateDimensionRadius`
|
||||
- **create_dimension_diameter**(center: [f64; 3], point: [f64; 3]) -> `Dimension` — `CreateDimensionDiameter`
|
||||
- **create_dimension_angular**(vertex: [f64; 3], first_point: [f64; 3], second_point: [f64; 3], arc_location: [f64; 3]) -> `Dimension` — `CreateDimensionAngular`
|
||||
|
||||
### Methods
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::id::ObjectId;
|
||||
use crate::ops::{BoolOp, Curve2Spec, DimensionSpec, EntitySpec, HatchSpec, InsertSpec, MTextSpec, PlacementSpec, RasterImageSpec, SolidPrimitive, TextSpec, ViewportSpec};
|
||||
use crate::ops::{BoolOp, Curve2Spec, DimensionAngularSpec, DimensionRadialSpec, DimensionSpec, EntitySpec, HatchSpec, InsertSpec, MTextSpec, PlacementSpec, RasterImageSpec, SolidPrimitive, TextSpec, ViewportSpec};
|
||||
|
||||
/// A typed write operation (plan §5). Each variant is ONE atomic host call = one
|
||||
/// undo step. Append new variants at the END only.
|
||||
@@ -48,4 +48,8 @@ pub enum Operation {
|
||||
CreateRasterImage(RasterImageSpec),
|
||||
// multi-profile solid
|
||||
Loft { profiles: Vec<ObjectId> },
|
||||
// dimension sub-types
|
||||
CreateDimensionRadius(DimensionRadialSpec),
|
||||
CreateDimensionDiameter(DimensionRadialSpec),
|
||||
CreateDimensionAngular(DimensionAngularSpec),
|
||||
}
|
||||
@@ -116,6 +116,26 @@ pub struct DimensionSpec {
|
||||
pub definition_point: [f64; 3],
|
||||
}
|
||||
|
||||
/// Construction spec for a radial/diameter `DIMENSION`: the circle center and a
|
||||
/// point on the circle (radius) or a chord point (diameter).
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DimensionRadialSpec {
|
||||
/// Center of the arc/circle (radius) or first chord point (diameter).
|
||||
pub center: [f64; 3],
|
||||
/// A point on the arc/circle (radius) or the far chord point (diameter).
|
||||
pub point: [f64; 3],
|
||||
}
|
||||
|
||||
/// Construction spec for a 3-point angular `DIMENSION`: vertex + one point on
|
||||
/// each leg, plus the dimension-arc location.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DimensionAngularSpec {
|
||||
pub vertex: [f64; 3],
|
||||
pub first_point: [f64; 3],
|
||||
pub second_point: [f64; 3],
|
||||
pub arc_location: [f64; 3],
|
||||
}
|
||||
|
||||
/// Construction spec for a `RASTER_IMAGE`: an image file placed at
|
||||
/// `insertion_point` with per-pixel world-unit vectors `u_vector`/`v_vector` and
|
||||
/// pixel `size` (width, height). The host auto-registers the ImageDefinition.
|
||||
@@ -187,6 +207,9 @@ impl crate::gen::Operation {
|
||||
SetViewportView { .. } => "SetViewportView",
|
||||
CreateRasterImage(_) => "CreateRasterImage",
|
||||
Loft { .. } => "Loft",
|
||||
CreateDimensionRadius(_) => "CreateDimensionRadius",
|
||||
CreateDimensionDiameter(_) => "CreateDimensionDiameter",
|
||||
CreateDimensionAngular(_) => "CreateDimensionAngular",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,20 @@ impl DocApiBackend for MockBackend {
|
||||
self.dimension_measurements.insert(id, d);
|
||||
Ok(id)
|
||||
}
|
||||
fn add_dimension_radius(&mut self, spec: &ocs_doc_api::ops::DimensionRadialSpec) -> ApiResult<ObjectId> {
|
||||
let id = self.alloc("Dimension");
|
||||
let d = ((spec.center[0] - spec.point[0]).powi(2) + (spec.center[1] - spec.point[1]).powi(2) + (spec.center[2] - spec.point[2]).powi(2)).sqrt();
|
||||
self.dimension_measurements.insert(id, d);
|
||||
Ok(id)
|
||||
}
|
||||
fn add_dimension_diameter(&mut self, spec: &ocs_doc_api::ops::DimensionRadialSpec) -> ApiResult<ObjectId> {
|
||||
self.add_dimension_radius(spec)
|
||||
}
|
||||
fn add_dimension_angular(&mut self, _spec: &ocs_doc_api::ops::DimensionAngularSpec) -> ApiResult<ObjectId> {
|
||||
let id = self.alloc("Dimension");
|
||||
self.dimension_measurements.insert(id, 90.0); // mock: 90 degrees
|
||||
Ok(id)
|
||||
}
|
||||
fn dimension_measurement(&self, id: ObjectId) -> ApiResult<f64> {
|
||||
self.dimension_measurements.get(&id).copied().ok_or(ApiError::UnknownId(id))
|
||||
}
|
||||
|
||||
@@ -64,6 +64,9 @@ fn operation_variants() -> Vec<String> {
|
||||
file_path: String::new(), insertion_point: [0.0; 3], u_vector: [0.0; 3], v_vector: [0.0; 3], size: [0.0; 2],
|
||||
}),
|
||||
Operation::Loft { profiles: vec![] },
|
||||
Operation::CreateDimensionRadius(ocs_doc_api::ops::DimensionRadialSpec { center: [0.0; 3], point: [0.0; 3] }),
|
||||
Operation::CreateDimensionDiameter(ocs_doc_api::ops::DimensionRadialSpec { center: [0.0; 3], point: [0.0; 3] }),
|
||||
Operation::CreateDimensionAngular(ocs_doc_api::ops::DimensionAngularSpec { vertex: [0.0; 3], first_point: [0.0; 3], second_point: [0.0; 3], arc_location: [0.0; 3] }),
|
||||
];
|
||||
// Derive the variant names by serializing a probe of each and reading the
|
||||
// bincode variant index -> name map is not available; instead match by
|
||||
@@ -153,6 +156,9 @@ const OPERATION_BASELINE: &[&str] = &[
|
||||
"SetViewportView",
|
||||
"CreateRasterImage",
|
||||
"Loft",
|
||||
"CreateDimensionRadius",
|
||||
"CreateDimensionDiameter",
|
||||
"CreateDimensionAngular",
|
||||
];
|
||||
|
||||
/// Recorded baseline of the `Query` variant order.
|
||||
|
||||
@@ -248,6 +248,34 @@ impl DocApiBackend for HostSession<'_> {
|
||||
Ok(handle_to_obj(handle))
|
||||
}
|
||||
|
||||
fn add_dimension_radius(&mut self, spec: &ocs_doc_api::ops::DimensionRadialSpec) -> ApiResult<ObjectId> {
|
||||
use acadrust::entities::{Dimension, DimensionRadius};
|
||||
use acadrust::types::Vector3;
|
||||
let v3 = |p: [f64; 3]| Vector3::new(p[0], p[1], p[2]);
|
||||
let dim = DimensionRadius::new(v3(spec.center), v3(spec.point));
|
||||
let handle = self.scene_mut().add_entity(EntityType::Dimension(Dimension::Radius(dim)));
|
||||
Ok(handle_to_obj(handle))
|
||||
}
|
||||
|
||||
fn add_dimension_diameter(&mut self, spec: &ocs_doc_api::ops::DimensionRadialSpec) -> ApiResult<ObjectId> {
|
||||
use acadrust::entities::{Dimension, DimensionDiameter};
|
||||
use acadrust::types::Vector3;
|
||||
let v3 = |p: [f64; 3]| Vector3::new(p[0], p[1], p[2]);
|
||||
let dim = DimensionDiameter::new(v3(spec.center), v3(spec.point));
|
||||
let handle = self.scene_mut().add_entity(EntityType::Dimension(Dimension::Diameter(dim)));
|
||||
Ok(handle_to_obj(handle))
|
||||
}
|
||||
|
||||
fn add_dimension_angular(&mut self, spec: &ocs_doc_api::ops::DimensionAngularSpec) -> ApiResult<ObjectId> {
|
||||
use acadrust::entities::{Dimension, DimensionAngular3Pt};
|
||||
use acadrust::types::Vector3;
|
||||
let v3 = |p: [f64; 3]| Vector3::new(p[0], p[1], p[2]);
|
||||
let mut dim = DimensionAngular3Pt::new(v3(spec.vertex), v3(spec.first_point), v3(spec.second_point));
|
||||
dim.definition_point = v3(spec.arc_location);
|
||||
let handle = self.scene_mut().add_entity(EntityType::Dimension(Dimension::Angular3Pt(dim)));
|
||||
Ok(handle_to_obj(handle))
|
||||
}
|
||||
|
||||
fn dimension_measurement(&self, id: ObjectId) -> ApiResult<f64> {
|
||||
let entity = self.document().get_entity(obj_to_handle(id)).ok_or(ApiError::UnknownId(id))?;
|
||||
let EntityType::Dimension(d) = entity else {
|
||||
@@ -258,6 +286,8 @@ impl DocApiBackend for HostSession<'_> {
|
||||
acadrust::entities::Dimension::Linear(x) => x.measurement(),
|
||||
acadrust::entities::Dimension::Radius(x) => x.measurement(),
|
||||
acadrust::entities::Dimension::Diameter(x) => x.measurement(),
|
||||
acadrust::entities::Dimension::Angular3Pt(x) => x.measurement_degrees(),
|
||||
acadrust::entities::Dimension::Angular2Ln(x) => x.measurement_degrees(),
|
||||
_ => return Err(ApiError::Unsupported("dimension measurement for this sub-type".into())),
|
||||
})
|
||||
}
|
||||
@@ -1081,6 +1111,43 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase 2c-ii: dimension sub-types (radius/diameter/angular) ──────────
|
||||
|
||||
#[test]
|
||||
fn phase2cii_dimension_radius_diameter_angular() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let mut host = HostSession::new(&mut app, 0);
|
||||
// Radius: center (0,0,0), point on circle (4,0,0) -> radius 4.
|
||||
let rad = new_id(&dispatch(&mut host, DocApiEnvelope::op(Operation::CreateDimensionRadius(
|
||||
ocs_doc_api::ops::DimensionRadialSpec { center: [0.0; 3], point: [4.0, 0.0, 0.0] }))).unwrap());
|
||||
// Diameter: chord points (0,0,0) and (6,0,0) -> diameter 6.
|
||||
let dia = new_id(&dispatch(&mut host, DocApiEnvelope::op(Operation::CreateDimensionDiameter(
|
||||
ocs_doc_api::ops::DimensionRadialSpec { center: [0.0; 3], point: [6.0, 0.0, 0.0] }))).unwrap());
|
||||
// Angular: vertex (0,0,0), leg points (10,0,0) and (0,10,0) -> 90 degrees.
|
||||
let ang = new_id(&dispatch(&mut host, DocApiEnvelope::op(Operation::CreateDimensionAngular(
|
||||
ocs_doc_api::ops::DimensionAngularSpec {
|
||||
vertex: [0.0; 3], first_point: [10.0, 0.0, 0.0], second_point: [0.0, 10.0, 0.0], arc_location: [5.0, 5.0, 0.0],
|
||||
}))).unwrap());
|
||||
|
||||
let receipt = dispatch(&mut host, DocApiEnvelope::queries(vec![
|
||||
Query::GetDimensionMeasurement { id: rad },
|
||||
Query::GetDimensionMeasurement { id: dia },
|
||||
Query::GetDimensionMeasurement { id: ang },
|
||||
])).unwrap();
|
||||
match &receipt.query_results[0] {
|
||||
QueryResult::DimensionMeasurement(v) => assert!((*v - 4.0).abs() < 1e-4, "radius {v}"),
|
||||
other => panic!("expected measurement, got {other:?}"),
|
||||
}
|
||||
match &receipt.query_results[1] {
|
||||
QueryResult::DimensionMeasurement(v) => assert!((*v - 6.0).abs() < 1e-4, "diameter {v}"),
|
||||
other => panic!("expected measurement, got {other:?}"),
|
||||
}
|
||||
match &receipt.query_results[2] {
|
||||
QueryResult::DimensionMeasurement(v) => assert!((*v - 90.0).abs() < 0.1, "angle {v}"),
|
||||
other => panic!("expected measurement, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase 5 remaining: typed raster image create ─────────────────────────
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user