ocs_doc_api: read-mostly family bounds (Leader/Mesh/Face3D/MLine/Helix)

- entity_bounds: vertex/point-carrying read-mostly families (Leader.vertices,
  Mesh.vertices, Face3D 4 corners, MLine vertex positions, Helix start point)
  via a points-to-AABB helper. Region/Body/Surface/MultiLeader stay Unsupported.
- IMPLEMENTATION.md: read-mostly bounds done.
- Test read_mostly_family_bounds_leader_and_mesh -> 618 host lib green.
This commit is contained in:
Sebastian committed 2026-09-06 13:46:41 +02:00
1 parent 6997dfe618
commit 28c8e0ef1a
3 files changed
+77 -2

No files matched your search

+6 -2
View File
@@ -106,8 +106,12 @@ tested: 615 host lib + 8 crate unit + 3 spec↔wire consistency tests green.
- **Done:** media/misc families report a **real `kind`** via `GetEntity`
(`RasterImage`, `Table`, `Leader`, `MultiLeader`, `MLine`, `Mesh`, `Helix`,
`Region`, `Body`, `Surface`, `Face3D`, `Dimension`, `Hatch`) instead of "Other";
`RasterImage` has coarse bounds (insertion + u·width + v·height); generic
`delete` works on all. Covered by `phase5_*` test.
generic `delete` works on all. Covered by `phase5_*` test.
- **Done (read-mostly bounds):** `bounds()` now works for the vertex/point-carrying
families — `Leader`, `Mesh`, `Face3D` (4 corners), `MLine` (vertex positions),
`Helix` (start point) — via a points-to-AABB helper (`read_mostly_family_bounds_*`
test). `RasterImage` has 4-corner bounds. `Region`/`Body`/`Surface`/`MultiLeader`
stay `Unsupported` for bounds (complex/nested geometry).
- **Done (typed create):** `create_raster_image(file_path, insertion, u, v, size)`
→ `Operation::CreateRasterImage` (host auto-registers the ImageDefinition), and
`create_table(insertion_point, data[row][col])` → `Operation::CreateTable`
+48
View File
@@ -1301,6 +1301,54 @@ mod tests {
}
}
// ── Read-mostly family bounds (Leader/Mesh/Face3D/MLine/Helix) ──────────
#[test]
fn read_mostly_family_bounds_leader_and_mesh() {
let mut app = OpenCADStudio::new_for_test();
let mut host = HostSession::new(&mut app, 0);
// Leader with vertices at (0,0,0),(5,5,0),(10,0,0) -> bounds x[0,10] y[0,5].
let mut leader = acadrust::entities::Leader::default();
leader.vertices = vec![
acadrust::types::Vector3::new(0.0, 0.0, 0.0),
acadrust::types::Vector3::new(5.0, 5.0, 0.0),
acadrust::types::Vector3::new(10.0, 0.0, 0.0),
];
let leader_h = host.document_mut().add_entity(EntityType::Leader(leader)).unwrap();
// Mesh with vertices in a unit cube at (2..3, 2..3, 0).
let mut mesh = acadrust::entities::Mesh::default();
mesh.vertices = vec![
acadrust::types::Vector3::new(2.0, 2.0, 0.0),
acadrust::types::Vector3::new(3.0, 2.0, 0.0),
acadrust::types::Vector3::new(3.0, 3.0, 0.0),
acadrust::types::Vector3::new(2.0, 3.0, 0.0),
];
let mesh_h = host.document_mut().add_entity(EntityType::Mesh(mesh)).unwrap();
let receipt = dispatch(&mut host, DocApiEnvelope::queries(vec![
Query::GetBounds { id: handle_to_obj(leader_h) },
Query::GetBounds { id: handle_to_obj(mesh_h) },
Query::GetEntity { id: handle_to_obj(leader_h) },
])).unwrap();
match &receipt.query_results[0] {
QueryResult::Bounds(b) => {
assert!((b.min[0] - 0.0).abs() < 1e-6 && (b.max[0] - 10.0).abs() < 1e-6, "{b:?}");
assert!((b.min[1] - 0.0).abs() < 1e-6 && (b.max[1] - 5.0).abs() < 1e-6, "{b:?}");
}
other => panic!("expected bounds, got {other:?}"),
}
match &receipt.query_results[1] {
QueryResult::Bounds(b) => {
assert!((b.min[0] - 2.0).abs() < 1e-6 && (b.max[0] - 3.0).abs() < 1e-6, "{b:?}");
}
other => panic!("expected bounds, got {other:?}"),
}
match &receipt.query_results[2] {
QueryResult::Entity(e) => assert_eq!(e.kind, "Leader"),
other => panic!("expected entity, got {other:?}"),
}
}
// ── Phase 5 remaining: typed raster image create ─────────────────────────
#[test]
+23
View File
@@ -473,8 +473,31 @@ pub fn entity_bounds(entity: Option<&EntityType>, id: ObjectId) -> ApiResult<Aab
}
Aabb { min, max }
}
// Read-mostly families with vertex/point data: bounds from their points.
EntityType::Leader(l) => points_aabb(l.vertices.iter().collect(), id)?,
EntityType::Mesh(m) => points_aabb(m.vertices.iter().collect(), id)?,
EntityType::Face3D(f) => points_aabb(
[&f.first_corner, &f.second_corner, &f.third_corner, &f.fourth_corner].into_iter().collect(),
id,
)?,
EntityType::MLine(m) => points_aabb(m.vertices.iter().map(|v| &v.position).collect(), id)?,
EntityType::Helix(h) => points_aabb([&h.start_point].into_iter().collect(), id)?,
// Ray/XLine are unbounded by definition — no finite bounds.
_ => return Err(ApiError::Unsupported("bounds for this entity family is not yet implemented".into())),
};
Ok(bb)
}
/// AABB over a set of 3D points. `UnknownId` if empty (no points to bound).
fn points_aabb(pts: Vec<&Vector3>, id: ObjectId) -> ApiResult<Aabb> {
if pts.is_empty() {
return Err(ApiError::UnknownId(id));
}
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)];
}
Ok(Aabb { min, max })
}