forked from 1b-agent/opencadstudio-upstream
ocs_doc_api: Phase 2b-c (linear Dimension) — Phase 2 complete
- CreateDimensionLinear(DimensionSpec) op + GetDimensionMeasurement query (append-only). - backend add_dimension_linear (Dimension::Linear via DimensionLinear::new) + dimension_measurement (linear/radius/diameter distance). - Dimension bounds from measured + definition points; Dimension handle + measurement(). - spec Dimension family; api_reference + binding_schema regenerated; mocks + baseline. - Test phase2c_dimension_linear_create_measurement_bounds -> 607 host lib + 8 crate + 3 consistency green. Phase 2 (full 2D + annotations) is now complete.
This commit is contained in:
1 parent
ba8e5ed0cd
commit
2dba0158ca
16 files changed
+209
-3
No files matched your search
@@ -68,7 +68,10 @@ Phase 2b (annotations/hatch/dimension typed ops), attributes + nested traversal
|
|||||||
- **Done (2b-b):** `Hatch` — `create_hatch(boundary, solid)` (`CreateHatch`,
|
- **Done (2b-b):** `Hatch` — `create_hatch(boundary, solid)` (`CreateHatch`,
|
||||||
validates ≥3 points), `boundary()` → loops (`GetHatchBoundary`), bounds from
|
validates ≥3 points), `boundary()` → loops (`GetHatchBoundary`), bounds from
|
||||||
boundary edges, delete. Covered by `phase2b_hatch_*` test.
|
boundary edges, delete. Covered by `phase2b_hatch_*` test.
|
||||||
- **Remaining (2b-c):** `Dimension` (typed sub-types).
|
- **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.
|
||||||
- **Unblocked:** `add_vertex` (polyline) and sweep profiles (Line/Circle/Arc/LwPolyline)
|
- **Unblocked:** `add_vertex` (polyline) and sweep profiles (Line/Circle/Arc/LwPolyline)
|
||||||
are supported (Phase 0).
|
are supported (Phase 0).
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ pub struct MockBackend {
|
|||||||
text_values: HashMap<ObjectId, String>,
|
text_values: HashMap<ObjectId, String>,
|
||||||
/// Stored hatch boundary loops.
|
/// Stored hatch boundary loops.
|
||||||
hatch_boundaries: HashMap<ObjectId, Vec<Vec<[f64; 2]>>>,
|
hatch_boundaries: HashMap<ObjectId, Vec<Vec<[f64; 2]>>>,
|
||||||
|
/// Stored dimension measurements.
|
||||||
|
dimension_measurements: HashMap<ObjectId, f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockBackend {
|
impl MockBackend {
|
||||||
@@ -166,6 +168,18 @@ impl DocApiBackend for MockBackend {
|
|||||||
fn hatch_boundary(&self, id: ObjectId) -> ApiResult<Vec<Vec<[f64; 2]>>> {
|
fn hatch_boundary(&self, id: ObjectId) -> ApiResult<Vec<Vec<[f64; 2]>>> {
|
||||||
self.hatch_boundaries.get(&id).cloned().ok_or(ApiError::UnknownId(id))
|
self.hatch_boundaries.get(&id).cloned().ok_or(ApiError::UnknownId(id))
|
||||||
}
|
}
|
||||||
|
fn add_dimension_linear(&mut self, spec: &ocs_doc_api::ops::DimensionSpec) -> ApiResult<ObjectId> {
|
||||||
|
let id = self.alloc("Dimension");
|
||||||
|
let d = ((spec.first_point[0] - spec.second_point[0]).powi(2)
|
||||||
|
+ (spec.first_point[1] - spec.second_point[1]).powi(2)
|
||||||
|
+ (spec.first_point[2] - spec.second_point[2]).powi(2))
|
||||||
|
.sqrt();
|
||||||
|
self.dimension_measurements.insert(id, d);
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
fn dimension_measurement(&self, id: ObjectId) -> ApiResult<f64> {
|
||||||
|
self.dimension_measurements.get(&id).copied().ok_or(ApiError::UnknownId(id))
|
||||||
|
}
|
||||||
fn add_vertex(&mut self, id: ObjectId, _at: usize, _point: [f64; 3]) -> ApiResult<()> {
|
fn add_vertex(&mut self, id: ObjectId, _at: usize, _point: [f64; 3]) -> ApiResult<()> {
|
||||||
if self.entity_exists(id) {
|
if self.entity_exists(id) {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -311,6 +311,27 @@ kind = "query"
|
|||||||
query = "GetHatchBoundary"
|
query = "GetHatchBoundary"
|
||||||
returns = "Vec<Vec<[f64; 2]>>"
|
returns = "Vec<Vec<[f64; 2]>>"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# DIMENSION FAMILY (phase 2c) — linear dimensions
|
||||||
|
# ============================================================================
|
||||||
|
[[family]]
|
||||||
|
name = "Dimension"
|
||||||
|
acadrust_variant = "Dimension"
|
||||||
|
handle = "Dimension"
|
||||||
|
collection = "curves"
|
||||||
|
description = "Linear dimension between two measured points. measurement() reads the distance; bounds from the measured + definition points."
|
||||||
|
|
||||||
|
[[family.constructor]]
|
||||||
|
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.method]]
|
||||||
|
name = "measurement"
|
||||||
|
kind = "query"
|
||||||
|
query = "GetDimensionMeasurement"
|
||||||
|
returns = "f64"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# CONTAINER FAMILY (phase 3) — block references
|
# CONTAINER FAMILY (phase 3) — block references
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ pub trait DocApiBackend {
|
|||||||
/// A hatch's boundary loops (outer + islands) as closed polylines.
|
/// A hatch's boundary loops (outer + islands) as closed polylines.
|
||||||
fn hatch_boundary(&self, id: ObjectId) -> ApiResult<Vec<Vec<[f64; 2]>>>;
|
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>;
|
||||||
|
|
||||||
|
/// A dimension's measured value (distance for linear/radius, degrees for angular).
|
||||||
|
fn dimension_measurement(&self, id: ObjectId) -> ApiResult<f64>;
|
||||||
|
|
||||||
/// Can `id` be modified in place right now (exists, is the expected family,
|
/// 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.
|
/// not on a locked layer)? Read-only pre-check used before mutations.
|
||||||
/// Default: existence + not-locked (backends narrow the family check).
|
/// Default: existence + not-locked (backends narrow the family check).
|
||||||
|
|||||||
@@ -221,6 +221,12 @@ pub fn apply_op<B: DocApiBackend>(b: &mut B, op: Operation) -> ApiResult<Receipt
|
|||||||
b.finalize_op();
|
b.finalize_op();
|
||||||
OpOutcome::NewId(id)
|
OpOutcome::NewId(id)
|
||||||
}
|
}
|
||||||
|
Operation::CreateDimensionLinear(spec) => {
|
||||||
|
b.push_undo(name);
|
||||||
|
let id = b.add_dimension_linear(spec)?;
|
||||||
|
b.finalize_op();
|
||||||
|
OpOutcome::NewId(id)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(Receipt {
|
Ok(Receipt {
|
||||||
outcome: Some(outcome),
|
outcome: Some(outcome),
|
||||||
@@ -260,6 +266,7 @@ pub fn apply_queries<B: DocApiBackend>(b: &mut B, queries: Vec<Query>) -> ApiRes
|
|||||||
Query::GetGeometryRevision => QueryResult::Revision(b.revision()),
|
Query::GetGeometryRevision => QueryResult::Revision(b.revision()),
|
||||||
Query::GetTextContent { id } => QueryResult::TextContent(b.text_content(id)?),
|
Query::GetTextContent { id } => QueryResult::TextContent(b.text_content(id)?),
|
||||||
Query::GetHatchBoundary { id } => QueryResult::HatchBoundary(b.hatch_boundary(id)?),
|
Query::GetHatchBoundary { id } => QueryResult::HatchBoundary(b.hatch_boundary(id)?),
|
||||||
|
Query::GetDimensionMeasurement { id } => QueryResult::DimensionMeasurement(b.dimension_measurement(id)?),
|
||||||
};
|
};
|
||||||
results.push(r);
|
results.push(r);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,6 +269,18 @@ handle!(Ray);
|
|||||||
handle!(XLine);
|
handle!(XLine);
|
||||||
handle!(Text);
|
handle!(Text);
|
||||||
handle!(MText);
|
handle!(MText);
|
||||||
|
handle!(Dimension);
|
||||||
|
|
||||||
|
impl Dimension {
|
||||||
|
/// The measured value of this dimension (distance for linear/radius, degrees
|
||||||
|
/// for angular).
|
||||||
|
pub fn measurement(&self) -> ApiResult<f64> {
|
||||||
|
match self.session.one_query(Query::GetDimensionMeasurement { id: self.id })? {
|
||||||
|
QueryResult::DimensionMeasurement(v) => Ok(v),
|
||||||
|
_ => Err(ApiError::Transport("unexpected measurement result".into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Entity {
|
impl Entity {
|
||||||
pub fn view(&self) -> ApiResult<EntityView> {
|
pub fn view(&self) -> ApiResult<EntityView> {
|
||||||
@@ -475,6 +487,16 @@ impl CurveCollection {
|
|||||||
Ok(Entity::new(self.session.clone(), id))
|
Ok(Entity::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> {
|
||||||
|
let receipt = self.session.apply_op(Operation::CreateDimensionLinear(crate::ops::DimensionSpec {
|
||||||
|
first_point, second_point, definition_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))
|
||||||
|
}
|
||||||
|
|
||||||
/// Single-line TEXT annotation.
|
/// Single-line TEXT annotation.
|
||||||
pub fn create_text(&self, value: &str, insertion_point: [f64; 3], height: f64, rotation: f64) -> ApiResult<Text> {
|
pub fn create_text(&self, value: &str, insertion_point: [f64; 3], height: f64, rotation: f64) -> ApiResult<Text> {
|
||||||
let receipt = self.session.apply_op(Operation::CreateText(crate::ops::TextSpec {
|
let receipt = self.session.apply_op(Operation::CreateText(crate::ops::TextSpec {
|
||||||
|
|||||||
@@ -125,6 +125,18 @@ Filled region over a closed polyline boundary. boundary() returns the loops; bou
|
|||||||
|
|
||||||
- **boundary**(``) -> `Vec<Vec<[f64; 2]>>` — query `GetHatchBoundary`
|
- **boundary**(``) -> `Vec<Vec<[f64; 2]>>` — query `GetHatchBoundary`
|
||||||
|
|
||||||
|
## `Dimension` (acadrust `Dimension`, collection `curves`)
|
||||||
|
|
||||||
|
Linear dimension between two measured points. measurement() reads the distance; bounds from the measured + definition points.
|
||||||
|
|
||||||
|
### Constructors (`doc.curves()`)
|
||||||
|
|
||||||
|
- **create_dimension_linear**(`first_point: [f64; 3], second_point: [f64; 3], definition_point: [f64; 3]`) -> `Dimension`
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
- **measurement**(``) -> `f64` — query `GetDimensionMeasurement`
|
||||||
|
|
||||||
## `Entity` (acadrust `Insert`, collection `entities`)
|
## `Entity` (acadrust `Insert`, collection `entities`)
|
||||||
|
|
||||||
Block reference. Place an existing BlockRecord by name; transform moves its insert point.
|
Block reference. Place an existing BlockRecord by name; transform moves its insert point.
|
||||||
|
|||||||
@@ -277,6 +277,25 @@
|
|||||||
],
|
],
|
||||||
"name": "Hatch"
|
"name": "Hatch"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"acadrust_variant": "Dimension",
|
||||||
|
"collection": "curves",
|
||||||
|
"handle": "Dimension",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"args": [],
|
||||||
|
"bulk": false,
|
||||||
|
"fixed": null,
|
||||||
|
"kind": "query",
|
||||||
|
"name": "measurement",
|
||||||
|
"op": null,
|
||||||
|
"query": "GetDimensionMeasurement",
|
||||||
|
"read_only": true,
|
||||||
|
"returns": "f64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Dimension"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"acadrust_variant": "Insert",
|
"acadrust_variant": "Insert",
|
||||||
"collection": "entities",
|
"collection": "entities",
|
||||||
@@ -361,7 +380,8 @@
|
|||||||
"Ray",
|
"Ray",
|
||||||
"XLine",
|
"XLine",
|
||||||
"Text",
|
"Text",
|
||||||
"MText"
|
"MText",
|
||||||
|
"Dimension"
|
||||||
],
|
],
|
||||||
"root": "DocApi"
|
"root": "DocApi"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::id::ObjectId;
|
use crate::id::ObjectId;
|
||||||
use crate::ops::{BoolOp, Curve2Spec, EntitySpec, HatchSpec, InsertSpec, MTextSpec, PlacementSpec, SolidPrimitive, TextSpec, ViewportSpec};
|
use crate::ops::{BoolOp, Curve2Spec, DimensionSpec, EntitySpec, HatchSpec, InsertSpec, MTextSpec, PlacementSpec, SolidPrimitive, TextSpec, ViewportSpec};
|
||||||
|
|
||||||
/// A typed write operation (plan §5). Each variant is ONE atomic host call = one
|
/// A typed write operation (plan §5). Each variant is ONE atomic host call = one
|
||||||
/// undo step. Append new variants at the END only.
|
/// undo step. Append new variants at the END only.
|
||||||
@@ -38,4 +38,6 @@ pub enum Operation {
|
|||||||
SetTextContent { id: ObjectId, value: String },
|
SetTextContent { id: ObjectId, value: String },
|
||||||
// hatch
|
// hatch
|
||||||
CreateHatch(HatchSpec),
|
CreateHatch(HatchSpec),
|
||||||
|
// dimensions
|
||||||
|
CreateDimensionLinear(DimensionSpec),
|
||||||
}
|
}
|
||||||
@@ -23,4 +23,6 @@ pub enum Query {
|
|||||||
GetTextContent { id: ObjectId },
|
GetTextContent { id: ObjectId },
|
||||||
/// The boundary loops of a Hatch (outer + islands) as polylines.
|
/// The boundary loops of a Hatch (outer + islands) as polylines.
|
||||||
GetHatchBoundary { id: ObjectId },
|
GetHatchBoundary { id: ObjectId },
|
||||||
|
/// The measured value of a Dimension (distance or angle).
|
||||||
|
GetDimensionMeasurement { id: ObjectId },
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,15 @@ pub struct HatchSpec {
|
|||||||
pub solid: bool,
|
pub solid: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construction spec for a linear `DIMENSION`: the two measured points and the
|
||||||
|
/// dimension-line definition point.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct DimensionSpec {
|
||||||
|
pub first_point: [f64; 3],
|
||||||
|
pub second_point: [f64; 3],
|
||||||
|
pub definition_point: [f64; 3],
|
||||||
|
}
|
||||||
|
|
||||||
/// A rigid similarity (rotation + translation + optional uniform scale/reflection)
|
/// A rigid similarity (rotation + translation + optional uniform scale/reflection)
|
||||||
/// as a plain-data mirror of `cadkernel::brep::transform`'s `Placement`
|
/// as a plain-data mirror of `cadkernel::brep::transform`'s `Placement`
|
||||||
/// (non-serde). Columns are the new basis axes; `origin` is the translation.
|
/// (non-serde). Columns are the new basis axes; `origin` is the translation.
|
||||||
@@ -159,6 +168,7 @@ impl crate::gen::Operation {
|
|||||||
CreateMText(_) => "CreateMText",
|
CreateMText(_) => "CreateMText",
|
||||||
SetTextContent { .. } => "SetTextContent",
|
SetTextContent { .. } => "SetTextContent",
|
||||||
CreateHatch(_) => "CreateHatch",
|
CreateHatch(_) => "CreateHatch",
|
||||||
|
CreateDimensionLinear(_) => "CreateDimensionLinear",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,8 @@ pub enum QueryResult {
|
|||||||
TextContent(String),
|
TextContent(String),
|
||||||
/// A hatch's boundary loops (outer + islands), each a closed polyline.
|
/// A hatch's boundary loops (outer + islands), each a closed polyline.
|
||||||
HatchBoundary(Vec<Vec<[f64; 2]>>),
|
HatchBoundary(Vec<Vec<[f64; 2]>>),
|
||||||
|
/// A dimension's measured value (distance for linear/radius, degrees for angular).
|
||||||
|
DimensionMeasurement(f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience: the query name for diagnostics.
|
/// Convenience: the query name for diagnostics.
|
||||||
@@ -62,6 +64,7 @@ impl crate::gen::Query {
|
|||||||
GetGeometryRevision => "GetGeometryRevision",
|
GetGeometryRevision => "GetGeometryRevision",
|
||||||
GetTextContent { .. } => "GetTextContent",
|
GetTextContent { .. } => "GetTextContent",
|
||||||
GetHatchBoundary { .. } => "GetHatchBoundary",
|
GetHatchBoundary { .. } => "GetHatchBoundary",
|
||||||
|
GetDimensionMeasurement { .. } => "GetDimensionMeasurement",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,8 @@ struct MockBackend {
|
|||||||
text_values: HashMap<ObjectId, String>,
|
text_values: HashMap<ObjectId, String>,
|
||||||
/// Stored hatch boundary loops.
|
/// Stored hatch boundary loops.
|
||||||
hatch_boundaries: HashMap<ObjectId, Vec<Vec<[f64; 2]>>>,
|
hatch_boundaries: HashMap<ObjectId, Vec<Vec<[f64; 2]>>>,
|
||||||
|
/// Stored dimension measurements.
|
||||||
|
dimension_measurements: HashMap<ObjectId, f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockBackend {
|
impl MockBackend {
|
||||||
@@ -106,6 +108,18 @@ impl DocApiBackend for MockBackend {
|
|||||||
fn hatch_boundary(&self, id: ObjectId) -> ApiResult<Vec<Vec<[f64; 2]>>> {
|
fn hatch_boundary(&self, id: ObjectId) -> ApiResult<Vec<Vec<[f64; 2]>>> {
|
||||||
self.hatch_boundaries.get(&id).cloned().ok_or(ApiError::UnknownId(id))
|
self.hatch_boundaries.get(&id).cloned().ok_or(ApiError::UnknownId(id))
|
||||||
}
|
}
|
||||||
|
fn add_dimension_linear(&mut self, spec: &ocs_doc_api::ops::DimensionSpec) -> ApiResult<ObjectId> {
|
||||||
|
let id = self.alloc("Dimension");
|
||||||
|
let d = ((spec.first_point[0] - spec.second_point[0]).powi(2)
|
||||||
|
+ (spec.first_point[1] - spec.second_point[1]).powi(2)
|
||||||
|
+ (spec.first_point[2] - spec.second_point[2]).powi(2))
|
||||||
|
.sqrt();
|
||||||
|
self.dimension_measurements.insert(id, d);
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
fn dimension_measurement(&self, id: ObjectId) -> ApiResult<f64> {
|
||||||
|
self.dimension_measurements.get(&id).copied().ok_or(ApiError::UnknownId(id))
|
||||||
|
}
|
||||||
fn add_vertex(&mut self, id: ObjectId, _at: usize, _point: [f64; 3]) -> ApiResult<()> {
|
fn add_vertex(&mut self, id: ObjectId, _at: usize, _point: [f64; 3]) -> ApiResult<()> {
|
||||||
if self.entity_exists(id) {
|
if self.entity_exists(id) {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ fn operation_variants() -> Vec<String> {
|
|||||||
Operation::CreateMText(ocs_doc_api::ops::MTextSpec { value: String::new(), insertion_point: [0.0; 3], height: 0.0 }),
|
Operation::CreateMText(ocs_doc_api::ops::MTextSpec { value: String::new(), insertion_point: [0.0; 3], height: 0.0 }),
|
||||||
Operation::SetTextContent { id: ObjectId::from_u64(0), value: String::new() },
|
Operation::SetTextContent { id: ObjectId::from_u64(0), value: String::new() },
|
||||||
Operation::CreateHatch(ocs_doc_api::ops::HatchSpec { boundary: vec![], solid: true }),
|
Operation::CreateHatch(ocs_doc_api::ops::HatchSpec { boundary: vec![], solid: true }),
|
||||||
|
Operation::CreateDimensionLinear(ocs_doc_api::ops::DimensionSpec { first_point: [0.0; 3], second_point: [0.0; 3], definition_point: [0.0; 3] }),
|
||||||
];
|
];
|
||||||
// Derive the variant names by serializing a probe of each and reading the
|
// Derive the variant names by serializing a probe of each and reading the
|
||||||
// bincode variant index -> name map is not available; instead match by
|
// bincode variant index -> name map is not available; instead match by
|
||||||
@@ -83,6 +84,7 @@ fn query_variants() -> Vec<String> {
|
|||||||
Query::GetGeometryRevision,
|
Query::GetGeometryRevision,
|
||||||
Query::GetTextContent { id: ObjectId::from_u64(0) },
|
Query::GetTextContent { id: ObjectId::from_u64(0) },
|
||||||
Query::GetHatchBoundary { id: ObjectId::from_u64(0) },
|
Query::GetHatchBoundary { id: ObjectId::from_u64(0) },
|
||||||
|
Query::GetDimensionMeasurement { id: ObjectId::from_u64(0) },
|
||||||
];
|
];
|
||||||
marker
|
marker
|
||||||
.iter()
|
.iter()
|
||||||
@@ -137,6 +139,7 @@ const OPERATION_BASELINE: &[&str] = &[
|
|||||||
"CreateMText",
|
"CreateMText",
|
||||||
"SetTextContent",
|
"SetTextContent",
|
||||||
"CreateHatch",
|
"CreateHatch",
|
||||||
|
"CreateDimensionLinear",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Recorded baseline of the `Query` variant order.
|
/// Recorded baseline of the `Query` variant order.
|
||||||
@@ -149,6 +152,7 @@ const QUERY_BASELINE: &[&str] = &[
|
|||||||
"GetGeometryRevision",
|
"GetGeometryRevision",
|
||||||
"GetTextContent",
|
"GetTextContent",
|
||||||
"GetHatchBoundary",
|
"GetHatchBoundary",
|
||||||
|
"GetDimensionMeasurement",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -238,6 +238,30 @@ impl DocApiBackend for HostSession<'_> {
|
|||||||
Ok(loops)
|
Ok(loops)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_dimension_linear(&mut self, spec: &ocs_doc_api::ops::DimensionSpec) -> ApiResult<ObjectId> {
|
||||||
|
use acadrust::entities::{Dimension, DimensionLinear};
|
||||||
|
use acadrust::types::Vector3;
|
||||||
|
let v3 = |p: [f64; 3]| Vector3::new(p[0], p[1], p[2]);
|
||||||
|
let mut dim = DimensionLinear::new(v3(spec.first_point), v3(spec.second_point));
|
||||||
|
dim.definition_point = v3(spec.definition_point);
|
||||||
|
let handle = self.scene_mut().add_entity(EntityType::Dimension(Dimension::Linear(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 {
|
||||||
|
return Err(ApiError::Unsupported("GetDimensionMeasurement is only for Dimension".into()));
|
||||||
|
};
|
||||||
|
// Distance for linear/radius/diameter; degrees for angular.
|
||||||
|
Ok(match d {
|
||||||
|
acadrust::entities::Dimension::Linear(x) => x.measurement(),
|
||||||
|
acadrust::entities::Dimension::Radius(x) => x.measurement(),
|
||||||
|
acadrust::entities::Dimension::Diameter(x) => x.measurement(),
|
||||||
|
_ => return Err(ApiError::Unsupported("dimension measurement for this sub-type".into())),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn add_vertex(&mut self, id: ObjectId, at: usize, point: [f64; 3]) -> ApiResult<()> {
|
fn add_vertex(&mut self, id: ObjectId, at: usize, point: [f64; 3]) -> ApiResult<()> {
|
||||||
let handle = obj_to_handle(id);
|
let handle = obj_to_handle(id);
|
||||||
let Some(entity) = self.document().get_entity(handle).cloned() else {
|
let Some(entity) = self.document().get_entity(handle).cloned() else {
|
||||||
@@ -905,6 +929,38 @@ mod tests {
|
|||||||
assert!((t.insertion_point.x - 15.0).abs() < 1e-6);
|
assert!((t.insertion_point.x - 15.0).abs() < 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Phase 2b-c: dimension ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn phase2c_dimension_linear_create_measurement_bounds() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
let mut host = HostSession::new(&mut app, 0);
|
||||||
|
// Linear dimension from (0,0,0) to (30,0,0) with the line at (0,5,0).
|
||||||
|
let dim = new_id(&dispatch(&mut host, DocApiEnvelope::op(Operation::CreateDimensionLinear(
|
||||||
|
ocs_doc_api::ops::DimensionSpec {
|
||||||
|
first_point: [0.0, 0.0, 0.0],
|
||||||
|
second_point: [30.0, 0.0, 0.0],
|
||||||
|
definition_point: [0.0, 5.0, 0.0],
|
||||||
|
}))).unwrap());
|
||||||
|
let receipt = dispatch(&mut host, DocApiEnvelope::queries(vec![
|
||||||
|
Query::GetDimensionMeasurement { id: dim },
|
||||||
|
Query::GetEntity { id: dim },
|
||||||
|
Query::GetBounds { id: dim },
|
||||||
|
])).unwrap();
|
||||||
|
match &receipt.query_results[0] {
|
||||||
|
QueryResult::DimensionMeasurement(v) => assert!((*v - 30.0).abs() < 1e-6, "measurement {v}"),
|
||||||
|
other => panic!("expected measurement, got {other:?}"),
|
||||||
|
}
|
||||||
|
match &receipt.query_results[1] {
|
||||||
|
QueryResult::Entity(e) => assert_eq!(e.kind, "Dimension"),
|
||||||
|
other => panic!("expected entity, got {other:?}"),
|
||||||
|
}
|
||||||
|
match &receipt.query_results[2] {
|
||||||
|
QueryResult::Bounds(b) => assert!((b.min[0] - 0.0).abs() < 1e-6 && (b.max[0] - 30.0).abs() < 1e-6, "{b:?}"),
|
||||||
|
other => panic!("expected bounds, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Phase 2b-b: hatch ────────────────────────────────────────────────────
|
// ── Phase 2b-b: hatch ────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -412,6 +412,16 @@ pub fn entity_bounds(entity: Option<&EntityType>, id: ObjectId) -> ApiResult<Aab
|
|||||||
min: [t.insertion_point.x, t.insertion_point.y - t.height, t.insertion_point.z],
|
min: [t.insertion_point.x, t.insertion_point.y - t.height, t.insertion_point.z],
|
||||||
max: [t.insertion_point.x + t.height * t.value.len() as f64, t.insertion_point.y, t.insertion_point.z],
|
max: [t.insertion_point.x + t.height * t.value.len() as f64, t.insertion_point.y, t.insertion_point.z],
|
||||||
},
|
},
|
||||||
|
EntityType::Dimension(acadrust::entities::Dimension::Linear(d)) => {
|
||||||
|
let pts = [d.first_point, d.second_point, d.definition_point];
|
||||||
|
let mut min = [f64::INFINITY; 3];
|
||||||
|
let mut max = [f64::NEG_INFINITY; 3];
|
||||||
|
for p in pts {
|
||||||
|
min = [min[0].min(p.x), min[1].min(p.y), min[2].min(p.z)];
|
||||||
|
max = [max[0].max(p.x), max[1].max(p.y), max[2].max(p.z)];
|
||||||
|
}
|
||||||
|
Aabb { min, max }
|
||||||
|
}
|
||||||
EntityType::Hatch(h) => {
|
EntityType::Hatch(h) => {
|
||||||
let mut min = [f64::INFINITY; 3];
|
let mut min = [f64::INFINITY; 3];
|
||||||
let mut max = [f64::NEG_INFINITY; 3];
|
let mut max = [f64::NEG_INFINITY; 3];
|
||||||
|
|||||||
Reference in new issue
Block a user