Files
OneCAD-Draft/src/scene/convert/solid3d_tess.rs
T
Hakan Seven 44a40eda0b fix(model): restore saved solid booleans
Reload exact B-reps from solid history or ACIS before model operations and pin the closed-cut kernel fix.
2026-08-23 19:35:48 +03:00

315 lines
8.8 KiB
Rust

use acadrust::entities::acis::{SabReader, SatBody, SatDocument};
use acadrust::entities::{Body, Region, Solid3D};
use cadkernel::brep::{self, Body as KernelBody};
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
fn tessellate_acis(
sat: &SatDocument,
name: String,
color: [f32; 4],
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
) -> Option<MeshLodSet> {
crate::scene::convert::acis_kernel::tessellate_sat(
sat,
name,
color,
facet_res,
chordal_deflection,
isolines,
)
}
pub(crate) fn body_transform(
sat: &SatDocument,
body_record: usize,
) -> Result<Option<([f64; 9], [f64; 3], f64)>, ()> {
let body = SatBody::from_record(sat.record(body_record).ok_or(())?).ok_or(())?;
let Some(transform_record) = body.transform().index() else {
return Ok(None);
};
let transform = sat.record(transform_record).ok_or(())?;
if transform.entity_type != "transform" {
return Err(());
}
let mut values = Vec::with_capacity(13);
for token in &transform.tokens {
if values.len() >= 13 {
break;
}
if let Some((components, len)) = token.coordinate_components() {
values.extend_from_slice(&components[..len]);
} else if let Some(value) = token.as_float() {
values.push(value);
} else if let Some(value) = token.as_integer() {
values.push(value as f64);
} else if let Some(text) = token.as_string() {
for word in text.split_ascii_whitespace() {
let Ok(value) = word.parse::<f64>() else {
break;
};
values.push(value);
if values.len() >= 13 {
break;
}
}
}
}
(values.len() >= 13
&& values[..13].iter().all(|value| value.is_finite())
&& values[12] > 0.0)
.then(|| {
(
[
values[0], values[1], values[2], values[3], values[4], values[5], values[6],
values[7], values[8],
],
[values[9], values[10], values[11]],
values[12],
)
})
.map(Some)
.ok_or(())
}
pub(crate) fn finalize_mesh(
name: String,
verts: Vec<[f64; 3]>,
normals: Vec<[f32; 3]>,
indices: Vec<u32>,
triangle_material_handles: Vec<Option<acadrust::Handle>>,
triangle_colors: Vec<Option<[f32; 4]>>,
color: [f32; 4],
xform: Option<([f64; 9], [f64; 3], f64)>,
) -> MeshModel {
let mut verts_high = Vec::with_capacity(verts.len());
let mut verts_low = Vec::with_capacity(verts.len());
for [x, y, z] in verts {
let [x, y, z] = match xform {
Some((matrix, translation, scale)) => [
scale * (x * matrix[0] + y * matrix[3] + z * matrix[6]) + translation[0],
scale * (x * matrix[1] + y * matrix[4] + z * matrix[7]) + translation[1],
scale * (x * matrix[2] + y * matrix[5] + z * matrix[8]) + translation[2],
],
None => [x, y, z],
};
let high = [x as f32, y as f32, z as f32];
verts_high.push(high);
verts_low.push([
(x - high[0] as f64) as f32,
(y - high[1] as f64) as f32,
(z - high[2] as f64) as f32,
]);
}
let normals = match xform {
Some((matrix, _, _)) => normals
.iter()
.map(|normal| {
let [x, y, z] = [normal[0] as f64, normal[1] as f64, normal[2] as f64];
let transformed = [
x * matrix[0] + y * matrix[3] + z * matrix[6],
x * matrix[1] + y * matrix[4] + z * matrix[7],
x * matrix[2] + y * matrix[5] + z * matrix[8],
];
let length = (transformed[0] * transformed[0]
+ transformed[1] * transformed[1]
+ transformed[2] * transformed[2])
.sqrt();
if length > 1e-9 {
[
(transformed[0] / length) as f32,
(transformed[1] / length) as f32,
(transformed[2] / length) as f32,
]
} else {
*normal
}
})
.collect(),
None => normals,
};
MeshModel {
name,
verts: verts_high,
verts_low,
normals,
indices,
triangle_material_handles,
triangle_colors,
color,
selected: false,
}
}
fn parse_acis(
sat_fn: impl FnOnce() -> Option<SatDocument>,
is_binary: bool,
sab_data: &[u8],
) -> Option<SatDocument> {
sat_fn().or_else(|| {
(is_binary && !sab_data.is_empty())
.then(|| SabReader::read(sab_data).ok())
.flatten()
})
}
pub fn kernel_body(solid: &Solid3D) -> Option<KernelBody> {
let sat = parse_acis(
|| solid.parse_sat(),
solid.acis_data.is_binary,
&solid.acis_data.sab_data,
)?;
let (mut bodies, loss) = cadkernel::acis::lift(&sat);
if !loss.is_empty() || bodies.len() != 1 {
return None;
}
let body = bodies.pop()?;
let source = body.provenance.source()?;
let Some((matrix, translation, scale)) = body_transform(&sat, source.index() as usize).ok()?
else {
return Some(body);
};
brep::transform(
&body,
&brep::Placement {
x_axis: [scale * matrix[0], scale * matrix[1], scale * matrix[2]],
y_axis: [scale * matrix[3], scale * matrix[4], scale * matrix[5]],
z_axis: [scale * matrix[6], scale * matrix[7], scale * matrix[8]],
origin: translation,
},
)
}
fn remap_acis_material_bindings(
set: &mut MeshLodSet,
acis: &acadrust::entities::AcisData,
) {
for lod in &mut set.lods {
for handle in lod.triangle_material_handles.iter_mut().flatten() {
let reference = handle.value() as i32;
if let Some(material_handle) = acis.materials.iter().find_map(|binding| {
(binding.absolute_reference == reference || binding.array_index == reference)
.then_some(binding.material_handle)
.flatten()
}) {
*handle = material_handle;
}
}
}
}
fn finish(
sat: SatDocument,
name: String,
color: [f32; 4],
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
acis: &acadrust::entities::AcisData,
) -> Option<MeshLodSet> {
let mut set = tessellate_acis(
&sat,
name,
color,
facet_res,
chordal_deflection,
isolines,
)?;
remap_acis_material_bindings(&mut set, acis);
Some(set)
}
pub fn tessellate_region(
region: &Region,
color: [f32; 4],
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
) -> Option<MeshLodSet> {
let sat = parse_acis(
|| region.parse_sat(),
region.acis_data.is_binary,
&region.acis_data.sab_data,
)?;
finish(
sat,
region.common.handle.value().to_string(),
color,
facet_res,
chordal_deflection,
isolines,
&region.acis_data,
)
}
pub fn tessellate_body(
body: &Body,
color: [f32; 4],
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
) -> Option<MeshLodSet> {
let sat = parse_acis(
|| body.parse_sat(),
body.acis_data.is_binary,
&body.acis_data.sab_data,
)?;
finish(
sat,
body.common.handle.value().to_string(),
color,
facet_res,
chordal_deflection,
isolines,
&body.acis_data,
)
}
pub fn tessellate_surface(
surface: &acadrust::entities::Surface,
color: [f32; 4],
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
) -> Option<MeshLodSet> {
let sat = parse_acis(
|| surface.parse_sat(),
surface.acis_data.is_binary,
&surface.acis_data.sab_data,
)?;
finish(
sat,
surface.common.handle.value().to_string(),
color,
facet_res,
chordal_deflection,
isolines,
&surface.acis_data,
)
}
pub fn tessellate_solid3d(
solid: &Solid3D,
color: [f32; 4],
facet_res: f64,
chordal_deflection: Option<f64>,
isolines: usize,
) -> Option<MeshLodSet> {
let sat = parse_acis(
|| solid.parse_sat(),
solid.acis_data.is_binary,
&solid.acis_data.sab_data,
)?;
finish(
sat,
solid.common.handle.value().to_string(),
color,
facet_res,
chordal_deflection,
isolines,
&solid.acis_data,
)
}