forked from 1b-agent/opencadstudio-upstream
ocs_doc_api: fix review findings (layouts, Helix bounds, points_aabb, mass-properties budget, docs)
- layouts.json: append the 6 missing ops (CreateDimensionRadius/Diameter/Angular,
CreateAttributeDefinition, CreateTable, CreateDimensionAngular2Ln); regenerate
binding_schema.json + api_reference.md wire vocabulary (all 28 ops).
- Helix bounds: bound the evaluated spline control polygon (was a degenerate
single-start-point AABB).
- points_aabb: empty -> Geometry::Empty('entity has no points to bound'), not
UnknownId (the entity exists; don't misclassify as stale/deleted).
- mass_properties: accuracy-first fine tessellation + graceful degradation to the
approximate meshes.metrics value when the cold-tess budget is exhausted (large
distinct-solid batches no longer hard-fail).
- IMPLEMENTATION.md: Volume/Centroid rows + accuracy paragraph + test count (618)
consistent with the final design.
- 618 host lib + 8 crate + 3 consistency green.
This commit is contained in:
1 parent
28c8e0ef1a
commit
ac54d6079b
6 files changed
+52
-16
No files matched your search
@@ -15,8 +15,8 @@ content).
|
||||
| Booleans | `intersect` / `union` / `subtract` | `SolidBoolean{op, a, b, erase_sources:true}` → `brep::combine` |
|
||||
| Transform | `transform(placement)` (in-place) | `Transform{id, placement}` → `brep::transform`; solids |
|
||||
| Bounds | `bounds()` | `GetBounds` → `brep::body_bounds` (lift-on-miss) |
|
||||
| Volume | `volume()` | `GetVolume` → `meshes.metrics` cache, mesh-divergence fallback |
|
||||
| Centroid | `centroid()` | `GetCentroid` → `meshes.metrics` cache, mesh-divergence fallback |
|
||||
| Volume | `volume()` | `GetVolume` → fine-tessellation `mesh_body(0.1, 1e-4)` (near-analytic); memoized per handle+epoch; budget-exhausted → approximate `meshes.metrics` |
|
||||
| Centroid | `centroid()` | `GetCentroid` → same accuracy path as `volume()` |
|
||||
| Delete | `delete()` / `delete_many` | `Delete`/`DeleteMany` |
|
||||
|
||||
### Minimal 2D (phase-1 set, used as profiles/inputs)
|
||||
@@ -53,7 +53,7 @@ variants append at enum end.
|
||||
(full 2D curves + annotations incl. dimension sub-types), Phase 3 (block references
|
||||
+ attributes + traversal + attribute definitions), Phase 4 (viewports + set_view +
|
||||
view query), Phase 5 (media read-mostly + typed raster image + typed table). All
|
||||
tested: 615 host lib + 8 crate unit + 3 spec↔wire consistency tests green.
|
||||
tested: 618 host lib + 8 crate unit + 3 spec↔wire consistency tests green.
|
||||
|
||||
### Phase 2 — full 2D + annotations
|
||||
- **Done (2a):** typed curve families `ArcCurve`/`Ellipse`/`Spline`/`Ray`/`XLine` —
|
||||
@@ -121,12 +121,13 @@ tested: 615 host lib + 8 crate unit + 3 spec↔wire consistency tests green.
|
||||
|
||||
## 🔧 Outstanding methods on supported families
|
||||
|
||||
**Completed (accuracy):** `GetVolume`/`GetCentroid` now compute via a **fine
|
||||
**Completed (accuracy):** `GetVolume`/`GetCentroid` compute via a **fine
|
||||
tessellation** (`mesh_body(0.1, 1e-4)`) for near-analytic results — the
|
||||
render-mesh metrics cache is the coarse display LOD and no longer shadows query
|
||||
accuracy; results memoized per (handle, geometry_epoch). Verified by
|
||||
`accurate_volume_centroid_sphere_and_cube` (sphere volume within 0.5% of 4/3πr³,
|
||||
centroid at centre, cube exact).
|
||||
render-mesh metrics cache is the coarse display LOD and does not drive queries;
|
||||
results memoized per (handle, geometry_epoch); when the cold-tess budget is
|
||||
exhausted the path degrades to the approximate `meshes.metrics` value rather than
|
||||
failing. Verified by `accurate_volume_centroid_sphere_and_cube` (sphere volume
|
||||
within 0.5% of 4/3πr³, centroid at centre, cube exact).
|
||||
|
||||
**Completed (outstanding methods):** `loft(profiles)` — `Operation::Loft{profiles}`,
|
||||
resolves each profile to curve sets (all-or-nothing), `brep::loft` (`loft_two_profiles_*`
|
||||
|
||||
@@ -278,6 +278,12 @@ Every `Operation` is ONE atomic write op (one undo step); every `Query` is read-
|
||||
- `SetViewportView { id: ObjectId, view_target: [f64; 3], view_height: f64 }`
|
||||
- `CreateRasterImage(RasterImageSpec)`
|
||||
- `Loft { profiles: Vec<ObjectId> }`
|
||||
- `CreateDimensionRadius(DimensionRadialSpec)`
|
||||
- `CreateDimensionDiameter(DimensionRadialSpec)`
|
||||
- `CreateDimensionAngular(DimensionAngularSpec)`
|
||||
- `CreateAttributeDefinition(AttributeDefinitionSpec)`
|
||||
- `CreateTable(TableSpec)`
|
||||
- `CreateDimensionAngular2Ln(DimensionAngularSpec)`
|
||||
|
||||
### `Query`
|
||||
|
||||
|
||||
@@ -428,7 +428,13 @@
|
||||
"SetAttribute { id: ObjectId, tag: String, value: String }",
|
||||
"SetViewportView { id: ObjectId, view_target: [f64; 3], view_height: f64 }",
|
||||
"CreateRasterImage(RasterImageSpec)",
|
||||
"Loft { profiles: Vec<ObjectId> }"
|
||||
"Loft { profiles: Vec<ObjectId> }",
|
||||
"CreateDimensionRadius(DimensionRadialSpec)",
|
||||
"CreateDimensionDiameter(DimensionRadialSpec)",
|
||||
"CreateDimensionAngular(DimensionAngularSpec)",
|
||||
"CreateAttributeDefinition(AttributeDefinitionSpec)",
|
||||
"CreateTable(TableSpec)",
|
||||
"CreateDimensionAngular2Ln(DimensionAngularSpec)"
|
||||
]
|
||||
},
|
||||
"Query": {
|
||||
|
||||
@@ -23,7 +23,13 @@
|
||||
"SetAttribute { id: ObjectId, tag: String, value: String }",
|
||||
"SetViewportView { id: ObjectId, view_target: [f64; 3], view_height: f64 }",
|
||||
"CreateRasterImage(RasterImageSpec)",
|
||||
"Loft { profiles: Vec<ObjectId> }"
|
||||
"Loft { profiles: Vec<ObjectId> }",
|
||||
"CreateDimensionRadius(DimensionRadialSpec)",
|
||||
"CreateDimensionDiameter(DimensionRadialSpec)",
|
||||
"CreateDimensionAngular(DimensionAngularSpec)",
|
||||
"CreateAttributeDefinition(AttributeDefinitionSpec)",
|
||||
"CreateTable(TableSpec)",
|
||||
"CreateDimensionAngular2Ln(DimensionAngularSpec)"
|
||||
]
|
||||
},
|
||||
"Query": {
|
||||
|
||||
+9
-1
@@ -633,10 +633,18 @@ impl HostSession<'_> {
|
||||
}
|
||||
// Accuracy path: fine tessellation so the divergence volume/centroid
|
||||
// converge to near-analytic (the render-mesh metrics cache is the coarse
|
||||
// LOD for display, NOT for query accuracy). Bounded by the cold-tess budget;
|
||||
// display LOD, NOT for query accuracy). Bounded by the cold-tess budget;
|
||||
// memoized per (handle, epoch) so repeated queries on the same solid are O(1).
|
||||
const COLD_TESS_BUDGET: usize = 256;
|
||||
if self.cold_tess_used >= COLD_TESS_BUDGET {
|
||||
// Budget exhausted: serve a documented-approximate value from the render
|
||||
// mesh metrics cache rather than failing (large distinct-solid batches
|
||||
// degrade gracefully instead of erroring). Accuracy-first up to the budget.
|
||||
if let Some(metrics) = self.scene().meshes.get(&handle).map(|m| m.metrics) {
|
||||
if metrics.volume.abs() > 0.0 {
|
||||
return Ok((metrics.volume, metrics.centroid));
|
||||
}
|
||||
}
|
||||
return Err(ApiError::Unsupported(
|
||||
"volume/centroid cold-tessellation budget exceeded for this session; query fewer distinct solids".into(),
|
||||
));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use acadrust::entities::{Arc, Circle, Ellipse, Line, LwPolyline, LwVertex, Point, Ray, Spline, XLine};
|
||||
use acadrust::types::{Vector2, Vector3};
|
||||
use acadrust::EntityType;
|
||||
use ocs_doc_api::{Aabb, ApiError, ApiResult, Curve2Spec, ObjectId};
|
||||
use ocs_doc_api::{Aabb, ApiError, ApiResult, Curve2Spec, GeometryErrorKind, ObjectId};
|
||||
|
||||
/// Convert a profile entity (Line/Circle/Arc/LwPolyline) to `geom2d::Curve`s for
|
||||
/// sweep ops (`Extrude`/`Revolve`). Z is dropped (profiles lie on the XY plane).
|
||||
@@ -481,17 +481,26 @@ pub fn entity_bounds(entity: Option<&EntityType>, id: ObjectId) -> ApiResult<Aab
|
||||
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)?,
|
||||
EntityType::Helix(h) => {
|
||||
// Bound the evaluated spline's control polygon (conservative); the
|
||||
// single start_point alone would be a degenerate zero-size box.
|
||||
if h.spline.control_points.is_empty() {
|
||||
points_aabb([&h.start_point].into_iter().collect(), id)?
|
||||
} else {
|
||||
points_aabb(h.spline.control_points.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> {
|
||||
/// AABB over a set of 3D points. A geometry-Empty error if there are no points
|
||||
/// to bound (the entity exists — do NOT misclassify as a stale/deleted id).
|
||||
fn points_aabb(pts: Vec<&Vector3>, _id: ObjectId) -> ApiResult<Aabb> {
|
||||
if pts.is_empty() {
|
||||
return Err(ApiError::UnknownId(id));
|
||||
return Err(ApiError::geometry(GeometryErrorKind::Empty, "entity has no points to bound"));
|
||||
}
|
||||
let mut min = [f64::INFINITY; 3];
|
||||
let mut max = [f64::NEG_INFINITY; 3];
|
||||
|
||||
Reference in new issue
Block a user