diff --git a/crates/ocs_doc_api/IMPLEMENTATION.md b/crates/ocs_doc_api/IMPLEMENTATION.md index 91dc6db3c..8eafeee40 100644 --- a/crates/ocs_doc_api/IMPLEMENTATION.md +++ b/crates/ocs_doc_api/IMPLEMENTATION.md @@ -75,10 +75,14 @@ variants append at enum end. viewport-view query (view target/center/scale DTOs). ### Phase 5 β€” media & misc (read-mostly) -- **Entities:** raster image, underlay, table, light. -- **Methods:** CRUD + property queries; few/no geometric actions. These families - largely stay read-only `EntityView` DTOs β€” typed ops only where the spec - declares an action mapping. +- **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. +- **Remaining:** typed create/property ops for `RasterImage`/`Table`/light; + these largely stay read-only `EntityView` DTOs (typed ops only where the spec + declares an action mapping). ## πŸ”§ Outstanding methods on supported families diff --git a/crates/ocs_doc_api/spec/entities.toml b/crates/ocs_doc_api/spec/entities.toml index 7b8de56ed..7eb4fc097 100644 --- a/crates/ocs_doc_api/spec/entities.toml +++ b/crates/ocs_doc_api/spec/entities.toml @@ -267,6 +267,25 @@ name = "create_viewport" args = [{ name = "center", ty = "[f64; 3]" }, { name = "width", ty = "f64" }, { name = "height", ty = "f64" }, { name = "view_target", ty = "[f64; 3]" }, { name = "view_height", ty = "f64" }] op = "CreateViewport" +# ============================================================================ +# MEDIA & MISC (phase 5, read-mostly) β€” read-only EntityView, no typed create. +# These families report a real `kind` via GetEntity and coarse bounds where +# computable; they have no typed construction/geometric ops in v1. +# ============================================================================ +[[family]] +name = "RasterImage" +acadrust_variant = "RasterImage" +handle = "Entity" +collection = "entities" +description = "Raster image (read-mostly): GetEntity kind + coarse bounds (insertion + u*width + v*height); generic delete. No typed create in v1." + +[[family]] +name = "Table" +acadrust_variant = "Table" +handle = "Entity" +collection = "entities" +description = "Table (read-only): GetEntity kind; generic delete. No typed create/bounds in v1." + # ============================================================================ # Cross-cutting ops (generic handles + bulk) β€” documented in build.rs # ============================================================================ diff --git a/crates/ocs_doc_api/src/gen/api_reference.md b/crates/ocs_doc_api/src/gen/api_reference.md index 0447dbbbb..71f95f743 100644 --- a/crates/ocs_doc_api/src/gen/api_reference.md +++ b/crates/ocs_doc_api/src/gen/api_reference.md @@ -103,6 +103,14 @@ Paper-space viewport: a widthΓ—height window at center looking at view_target wi - **create_viewport**(`center: [f64; 3], width: f64, height: f64, view_target: [f64; 3], view_height: f64`) -> `Entity` +## `Entity` (acadrust `RasterImage`, collection `entities`) + +Raster image (read-mostly): GetEntity kind + coarse bounds (insertion + u*width + v*height); generic delete. No typed create in v1. + +## `Entity` (acadrust `Table`, collection `entities`) + +Table (read-only): GetEntity kind; generic delete. No typed create/bounds in v1. + ## Generic methods (every handle) Every typed handle (`Solid`, `Line`, `Circle`, `Polyline`, `Point`, `Entity`) is a diff --git a/crates/ocs_doc_api/src/gen/binding_schema.json b/crates/ocs_doc_api/src/gen/binding_schema.json index 5999bc28a..179c357d9 100644 --- a/crates/ocs_doc_api/src/gen/binding_schema.json +++ b/crates/ocs_doc_api/src/gen/binding_schema.json @@ -201,6 +201,20 @@ "handle": "Entity", "methods": [], "name": "Viewport" + }, + { + "acadrust_variant": "RasterImage", + "collection": "entities", + "handle": "Entity", + "methods": [], + "name": "RasterImage" + }, + { + "acadrust_variant": "Table", + "collection": "entities", + "handle": "Entity", + "methods": [], + "name": "Table" } ], "layouts": { diff --git a/src/app/doc_api.rs b/src/app/doc_api.rs index 7935c667e..bbb3ac1e1 100644 --- a/src/app/doc_api.rs +++ b/src/app/doc_api.rs @@ -679,6 +679,43 @@ mod tests { assert!((v.center.x - 60.0).abs() < 1e-6); } + // ── Phase 5: media & misc (read-mostly) ────────────────────────────────── + + #[test] + fn phase5_media_entities_read_kind_and_bounds() { + let mut app = OpenCADStudio::new_for_test(); + let mut host = HostSession::new(&mut app, 0); + // Insert a RasterImage directly (read-mostly: DocApi reads kind + bounds). + let img = { + use acadrust::entities::RasterImage; + use acadrust::types::{Vector2, Vector3}; + let mut img = RasterImage::default(); + img.insertion_point = Vector3::new(10.0, 20.0, 0.0); + img.u_vector = Vector3::new(0.5, 0.0, 0.0); // 0.5 world-units/pixel in X + img.v_vector = Vector3::new(0.0, 0.5, 0.0); + img.size = Vector2::new(100.0, 50.0); + host.document_mut().add_entity(EntityType::RasterImage(img)).unwrap() + }; + let img_id = handle_to_obj(img); + // GetEntity reports kind "RasterImage"; bounds = insertion + u*100 + v*50 = (60, 45). + let receipt = dispatch(&mut host, DocApiEnvelope::queries(vec![ + Query::GetEntity { id: img_id }, Query::GetBounds { id: img_id }])).unwrap(); + match &receipt.query_results[0] { + QueryResult::Entity(e) => assert_eq!(e.kind, "RasterImage"), + other => panic!("expected entity, got {other:?}"), + } + match &receipt.query_results[1] { + QueryResult::Bounds(b) => { + assert!((b.min[0] - 10.0).abs() < 1e-6 && (b.max[0] - 60.0).abs() < 1e-6, "{b:?}"); + assert!((b.min[1] - 20.0).abs() < 1e-6 && (b.max[1] - 45.0).abs() < 1e-6, "{b:?}"); + } + other => panic!("expected bounds, got {other:?}"), + } + // Generic delete works on media entities (read-mostly, but deletable). + dispatch(&mut host, DocApiEnvelope::op(Operation::Delete { id: img_id })).unwrap(); + assert!(host.document().get_entity(img).is_none()); + } + // ── Phase 3: containers (block references) ─────────────────────────────── #[test] diff --git a/src/app/doc_api_convert.rs b/src/app/doc_api_convert.rs index 87891aa4c..38bea725c 100644 --- a/src/app/doc_api_convert.rs +++ b/src/app/doc_api_convert.rs @@ -250,6 +250,20 @@ pub fn entity_kind_name(entity: &EntityType) -> &'static str { EntityType::Viewport(_) => "Viewport", EntityType::Text(_) => "Text", EntityType::MText(_) => "MText", + // Media & misc (phase 5, read-mostly): named so GetEntity reports a real kind. + EntityType::RasterImage(_) => "RasterImage", + EntityType::Table(_) => "Table", + EntityType::Leader(_) => "Leader", + EntityType::MultiLeader(_) => "MultiLeader", + EntityType::MLine(_) => "MLine", + EntityType::Mesh(_) => "Mesh", + EntityType::Helix(_) => "Helix", + EntityType::Region(_) => "Region", + EntityType::Body(_) => "Body", + EntityType::Surface(_) => "Surface", + EntityType::Face3D(_) => "Face3D", + EntityType::Dimension(_) => "Dimension", + EntityType::Hatch(_) => "Hatch", _ => "Other", } } @@ -329,6 +343,16 @@ pub fn entity_bounds(entity: Option<&EntityType>, id: ObjectId) -> ApiResult { + // Coarse image rect: origin + u*width + v*height (in world units). + let w = img.u_vector.x * img.size.x + img.v_vector.x * img.size.y; + let h = img.u_vector.y * img.size.x + img.v_vector.y * img.size.y; + let (x0, y0) = (img.insertion_point.x, img.insertion_point.y); + Aabb { + min: [x0.min(x0 + w), y0.min(y0 + h), img.insertion_point.z], + max: [x0.max(x0 + w), y0.max(y0 + h), img.insertion_point.z], + } + } // Ray/XLine are unbounded by definition β€” no finite bounds. _ => return Err(ApiError::Unsupported("bounds for this entity family is not yet implemented".into())), };