forked from 1b-agent/opencadstudio-upstream
Complete Surface: plane surfaces, extrusions, isolines and moves
Adds SurfaceFromProfile and Extrude kernel operations (doc.solids.surface and doc.solids.extrude), accepts surfaces in Transform, and maps Surface with editable isolines. Fixes a real bug found by the test: the surface kind field overwrote the entity-type kind key in Python dicts, so it is exposed as surface_kind, with a catalog test guarding against name collisions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
918351f264
commit
9cd6653a58
14 files changed
+441
-34
No files matched your search
@@ -793,6 +793,7 @@ fn generate_entity_coverage(out_dir: &Path, registry: &TypeRegistry) {
|
||||
| ("Helix", _)
|
||||
| ("RasterImage", _)
|
||||
| ("Wipeout", _)
|
||||
| ("Surface", "u_isolines" | "v_isolines")
|
||||
| ("Ole2Frame", _)
|
||||
| ("Underlay", _)
|
||||
| ("Viewport", _)
|
||||
|
||||
@@ -502,6 +502,11 @@
|
||||
],
|
||||
"Region": [
|
||||
"layer"
|
||||
],
|
||||
"Surface": [
|
||||
"layer",
|
||||
"u_isolines",
|
||||
"v_isolines"
|
||||
]
|
||||
},
|
||||
"readable": {
|
||||
@@ -627,6 +632,12 @@
|
||||
"uid",
|
||||
"point_of_reference",
|
||||
"history_handle"
|
||||
],
|
||||
"Surface": [
|
||||
"surface_kind",
|
||||
"modeler_format_version",
|
||||
"point_of_reference",
|
||||
"history_handle"
|
||||
]
|
||||
},
|
||||
"aliases": {
|
||||
@@ -643,6 +654,9 @@
|
||||
"MText": {
|
||||
"text": "value",
|
||||
"insertion": "insertion_point"
|
||||
},
|
||||
"Surface": {
|
||||
"surface_kind": "kind"
|
||||
}
|
||||
},
|
||||
"synthetic": {
|
||||
|
||||
@@ -1245,6 +1245,24 @@ fn validate_ole_frame(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Surface: only the display isoline density is scriptable; the geometry lives
|
||||
/// in the ACIS payload that the geometry kernel owns.
|
||||
#[cfg(feature = "host")]
|
||||
fn validate_surface(
|
||||
old: Option<&crate::host::acadrust::entities::Surface>,
|
||||
new: &crate::host::acadrust::entities::Surface,
|
||||
) -> Result<(), String> {
|
||||
if old.is_some_and(|old| old.u_isolines == new.u_isolines && old.v_isolines == new.v_isolines) {
|
||||
return Ok(());
|
||||
}
|
||||
for (name, value) in [("u_isolines", new.u_isolines), ("v_isolines", new.v_isolines)] {
|
||||
if !(0..=200).contains(&value) {
|
||||
return Err(format!("Surface.{name} must be between 0 and 200"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate newly created geometry for the kinds whose mapped fields have
|
||||
/// host checks. Called by the Python add path before an entity enters the document.
|
||||
#[cfg(feature = "host")]
|
||||
@@ -1371,6 +1389,7 @@ pub fn validate_new_canvas_entity(entity: &crate::host::EntityType) -> Result<()
|
||||
}
|
||||
}
|
||||
EntityType::Ole2Frame(value) => validate_ole_frame(None, value)?,
|
||||
EntityType::Surface(value) => validate_surface(None, value)?,
|
||||
EntityType::AttributeDefinition(value) => {
|
||||
finite_vector(
|
||||
"AttributeDefinition.insertion_point",
|
||||
@@ -1745,6 +1764,7 @@ pub fn validate_entity_mutation(
|
||||
return Err("Ole2Frame embedded storage cannot change".into());
|
||||
}
|
||||
}
|
||||
(EntityType::Surface(old), EntityType::Surface(new)) => validate_surface(Some(old), new)?,
|
||||
(EntityType::MLine(old), EntityType::MLine(new)) => validate_mline(Some(old), new)?,
|
||||
(EntityType::Leader(old), EntityType::Leader(new)) => validate_leader(Some(old), new)?,
|
||||
(EntityType::AttributeDefinition(old), EntityType::AttributeDefinition(new)) => {
|
||||
@@ -2486,6 +2506,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exposed_property_names_are_unique_per_kind() {
|
||||
// A field named like a base key (`kind`, `handle`, `layer`, ...) would
|
||||
// overwrite it in every Python dict; such fields must be renamed.
|
||||
let catalog: EntityCoverageCatalog =
|
||||
serde_json::from_str(get_embedded_entity_coverage_json()).unwrap();
|
||||
for entry in &catalog.entity_kinds {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for property in entry.properties.iter().filter(|p| p.model_access != ModelAccess::Unmapped) {
|
||||
assert!(seen.insert(property.name.clone()), "{}: duplicate exposed name {}", entry.kind, property.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_kinds_have_no_editable_properties() {
|
||||
let catalog: EntityCoverageCatalog =
|
||||
@@ -2535,6 +2569,7 @@ mod tests {
|
||||
| "Solid3D"
|
||||
| "Body"
|
||||
| "Region"
|
||||
| "Surface"
|
||||
)
|
||||
{
|
||||
assert!(
|
||||
@@ -2824,6 +2859,8 @@ mod tests {
|
||||
"Ole2Frame.lower_right_corner".to_owned(),
|
||||
"Ole2Frame.lock_aspect".to_owned(),
|
||||
"Ole2Frame.is_paper_space".to_owned(),
|
||||
"Surface.u_isolines".to_owned(),
|
||||
"Surface.v_isolines".to_owned(),
|
||||
"Underlay.underlay_type".to_owned(),
|
||||
"Underlay.definition_handle".to_owned(),
|
||||
"Underlay.insertion_point".to_owned(),
|
||||
|
||||
@@ -90,6 +90,11 @@ pub enum SolidOperation {
|
||||
/// `layer`, or on the source's layer when `None`; `delete_source` erases
|
||||
/// the profile afterwards, as the REGION command does.
|
||||
RegionFromProfile { source: Handle, layer: Option<String>, delete_source: bool },
|
||||
/// Build a plane surface from one closed planar profile entity.
|
||||
SurfaceFromProfile { source: Handle, layer: Option<String>, delete_source: bool },
|
||||
/// Extrude a planar profile along `direction`: a solid when the profile is
|
||||
/// closed, a surface when it is open.
|
||||
Extrude { source: Handle, direction: [f64; 3], layer: Option<String>, delete_source: bool },
|
||||
}
|
||||
|
||||
/// Value of a host-managed drafting or document setting exposed to plugins.
|
||||
|
||||
@@ -115,6 +115,8 @@ mod tests {
|
||||
},
|
||||
SolidOperation::Transform { handle: acadrust::Handle::new(7), matrix: [1.0; 16] },
|
||||
SolidOperation::RegionFromProfile { source: acadrust::Handle::new(3), layer: None, delete_source: true },
|
||||
SolidOperation::SurfaceFromProfile { source: acadrust::Handle::new(4), layer: Some("S".into()), delete_source: false },
|
||||
SolidOperation::Extrude { source: acadrust::Handle::new(5), direction: [0.0, 0.0, 2.0], layer: None, delete_source: true },
|
||||
] {
|
||||
let bytes = bincode::serialize(&PluginRequest::SolidOperation { operation: operation.clone() }).unwrap();
|
||||
assert!(matches!(bincode::deserialize::<PluginRequest>(&bytes).unwrap(),
|
||||
|
||||
Reference in new issue
Block a user