feat(model): build and read solids with the geometry kernel

Every 3-D solid OCS makes or opens now goes through cadkernel, which holds
analytic surfaces — a cylinder is a cylinder record, not a spline that looks
like one. That is what lets a solid built here be written back out as exact
ACIS instead of a triangulation, and a facetted export is a one-way door: the
next application to open the file would have no way back to the circle.

The Model tab's seven primitives, the Design-group booleans, 3DROTATE,
3DMIRROR, 3DALIGN, SLICE, SECTION, POLYSOLID and PYRAMID all come from the
kernel now, and `scene.solid_models` holds a `Body`. A mirrored solid needed
its own care before; the kernel reverses the loops on the way through, so it
comes back the right way out rather than lighting black.

EXTRUDE and REVOLVE sweep the profile `entities::curve` already defines, so a
circle, an arc-bulged polyline and a closed spline arrive as the same thing.
A circle extrudes into four cylinder patches rather than a run of chords, and
a profile turned about an axis becomes a cone, a sphere or a torus. A spline
profile has no analytic side wall and is refused rather than approximated.

ACIS goes both ways through the codec's own bridge: `lift` to read and
`append` to write. That drops the 679-line truck conversion and the planar-
only exporter, and the tessellator keeps its bespoke sampler as a fallback for
whatever the kernel cannot yet express — reported rather than passed off as
whole, since a mesh missing a wall looks finished.

truck-shapeops is no longer used and is gone from the manifest. truck-modeling
stays for now: fourteen files use it as a 3-D NURBS evaluator, which the
kernel has no answer for yet, and SWEEP and LOFT have no kernel operation
behind them either.

The two `automation` test failures are unchanged from before this and are not
caused by it.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5 committed 2026-08-10 00:32:34 +03:00
1 parent 8016b79ddf
commit 1f642bb40a
15 files changed
+905 -1627

No files matched your search

+36 -104
View File
@@ -2637,62 +2637,25 @@ impl OpenCADStudio {
height,
color,
} => {
use crate::entities::traits::EntityTypeOps;
use crate::modules::insert::solid3d_cmds::empty_solid3d;
use crate::scene::convert::acad_to_truck::TruckObject;
use crate::scene::convert::truck_tess;
use truck_modeling::builder;
use truck_modeling::Vector3 as TruckVec3;
use crate::scene::model::{solid_model, sweep_model};
let entity_opt = self.tabs[i].scene.document.get_entity(handle).cloned();
if let Some(entity) = entity_opt {
let truck_entity = entity.to_truck_entity(&self.tabs[i].scene.document);
let result = truck_entity.and_then(|te| {
match te.object {
TruckObject::Contour(wire) => {
// Attach a planar face to the wire profile, then sweep.
let face = builder::try_attach_plane(&[wire]).ok()?;
// tsweep(Face) → Solid
let solid =
builder::tsweep(&face, TruckVec3::new(0.0, 0.0, height as f64));
match truck_tess::tessellate_solid(&solid) {
truck_tess::TruckTessResult::Mesh {
verts,
verts_low,
normals,
indices,
} => Some((
crate::scene::model::mesh_model::MeshModel {
name: String::new(),
verts,
verts_low,
normals,
indices,
triangle_material_handles: Vec::new(),
triangle_colors: Vec::new(),
color,
selected: false,
},
solid,
)),
_ => None,
}
}
_ => None,
}
});
if let Some((mut mesh, solid)) = result {
// The kernel sweeps the profile into analytic surfaces —
// a straight run becomes a plane and an arc a cylinder —
// so the solid saves as exact ACIS rather than facets.
let result = sweep_model::extruded(&entity, height as f64)
.and_then(|body| Some((solid_model::mesh_from_solid(&body, color)?, body)));
if let Some((mesh, solid)) = result {
let pending = self.begin_undo(i, "EXTRUDE", 1, true);
let new_entity = empty_solid3d();
let new_handle = self.tabs[i].scene.add_entity(new_entity);
mesh.name = format!("{}", new_handle.value());
self.tabs[i]
.scene
.meshes
.insert(new_handle, crate::scene::MeshLodSet::from_single(mesh));
// Keep the truck B-rep so the save path can export exact
// ACIS geometry (else the solid is dropped by other CAD apps).
self.tabs[i].scene.solid_models.insert(new_handle, solid);
let mut s3d = empty_solid3d();
if let acadrust::EntityType::Solid3D(inner) = &mut s3d {
inner.wires = solid_model::edge_wires(&solid);
}
let new_handle = self.tabs[i].scene.add_entity(s3d);
self.tabs[i].scene.register_solid_model(new_handle, solid);
let _ = mesh;
self.tabs[i].dirty = true;
self.command_line.push_output(crate::t!("EXTRUDE: solid created.").as_ref());
if let Some(pd) = pending {
@@ -2718,65 +2681,34 @@ impl OpenCADStudio {
angle_deg,
color,
} => {
use crate::entities::traits::EntityTypeOps;
use crate::modules::insert::solid3d_cmds::empty_solid3d;
use crate::scene::convert::acad_to_truck::TruckObject;
use crate::scene::convert::truck_tess;
use truck_modeling::builder;
use truck_modeling::{Point3, Rad, Vector3 as TruckVec3};
use crate::scene::model::{solid_model, sweep_model};
let entity_opt = self.tabs[i].scene.document.get_entity(handle).cloned();
if let Some(entity) = entity_opt {
let truck_entity = entity.to_truck_entity(&self.tabs[i].scene.document);
let result = truck_entity.and_then(|te| {
let wire: Option<truck_modeling::Wire> = match te.object {
TruckObject::Contour(w) => Some(w),
TruckObject::Curve(e) => Some(std::iter::once(e).collect()),
_ => None,
};
let wire = wire?;
let origin = Point3::new(
// A line turned about the axis sweeps into a plane, a
// cylinder or a cone, and an arc into a sphere or a
// torus, so a revolved solid keeps exact geometry too.
let result = sweep_model::revolved(
&entity,
[
axis_start.x as f64,
axis_start.y as f64,
axis_start.z as f64,
);
let dir = (axis_end - axis_start).normalize();
let axis = TruckVec3::new(dir.x as f64, dir.y as f64, dir.z as f64);
let shell = builder::rsweep(
&wire,
origin,
axis,
Rad(angle_deg.to_radians() as f64),
);
match truck_tess::tessellate_shell(&shell) {
truck_tess::TruckTessResult::Mesh {
verts,
verts_low,
normals,
indices,
} => Some(crate::scene::model::mesh_model::MeshModel {
name: String::new(),
verts,
verts_low,
normals,
indices,
triangle_material_handles: Vec::new(),
triangle_colors: Vec::new(),
color,
selected: false,
}),
_ => None,
}
});
if let Some(mut mesh) = result {
],
[axis_end.x as f64, axis_end.y as f64, axis_end.z as f64],
(angle_deg as f64).to_radians(),
)
.and_then(|body| Some((solid_model::mesh_from_solid(&body, color)?, body)));
if let Some((mesh, solid)) = result {
let pending = self.begin_undo(i, "REVOLVE", 1, true);
let new_entity = empty_solid3d();
let new_handle = self.tabs[i].scene.add_entity(new_entity);
mesh.name = format!("{}", new_handle.value());
self.tabs[i]
.scene
.meshes
.insert(new_handle, crate::scene::MeshLodSet::from_single(mesh));
let mut s3d = empty_solid3d();
if let acadrust::EntityType::Solid3D(inner) = &mut s3d {
inner.wires = solid_model::edge_wires(&solid);
}
let new_handle = self.tabs[i].scene.add_entity(s3d);
self.tabs[i].scene.register_solid_model(new_handle, solid);
let _ = mesh;
self.tabs[i].dirty = true;
self.command_line
.push_output(crate::tf!("REVOLVE: solid created ({:.0}°).", angle_deg).as_ref());
@@ -2788,14 +2720,14 @@ impl OpenCADStudio {
.push_error(crate::t!("REVOLVE: could not revolve profile.").as_ref());
}
} else {
self.command_line.push_error(crate::t!("REVOLVE: entity not found.").as_ref());
self.command_line
.push_error(crate::t!("REVOLVE: entity not found.").as_ref());
}
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.tabs[i].scene.clear_preview_wire();
self.restore_pre_cmd_tangent();
}
// ── SWEEP ──────────────────────────────────────────────────────
CmdResult::SweepEntity {
profile_handle,