Complete LOFT commands, surfaces, previews and editable history

This commit is contained in:
ramox81 committed 2026-09-04 13:03:33 +03:00
1 parent 8e3d851133
commit 479bf87988
15 files changed
+1505 -161

No files matched your search

+151 -56
View File
@@ -54,10 +54,84 @@ pub(crate) use boundary::{
// other tessellation code); re-exported here so this root and sibling topic
// modules (each does `use super::*`) keep referencing them unqualified.
pub(crate) use convert::tess::{
entity_aabb, entity_world_aabb_f64, is_unindexable_entity, tessellate_entity,
entity_aabb, entity_world_aabb_f64, is_unindexable_entity,
tessellate_entity_dim_text,
};
/// Keep construction-history curves in the same per-entity tessellation memo
/// as their parent, so navigation never rebuilds them on every frame.
#[allow(clippy::too_many_arguments)]
pub(crate) fn tessellate_entity(
document: &CadDocument,
selected: &HashSet<Handle>,
active_viewport: Option<Handle>,
bg_color: [f32; 4],
anno_scale: f32,
annotation_scale_handle: Option<Handle>,
entity: &EntityType,
block_cache: Option<&cache::block_cache::BlockCache>,
view_aabb: Option<[f32; 4]>,
world_per_pixel: Option<f32>,
paper_space: bool,
) -> Vec<WireModel> {
let mut wires = convert::tess::tessellate_entity(
document, selected, active_viewport, bg_color, anno_scale,
annotation_scale_handle, entity, block_cache, view_aabb,
world_per_pixel, paper_space,
);
if matches!(entity, EntityType::Solid3D(_) | EntityType::Surface(_)) {
let handle = entity.common().handle;
for source in model::solid_history::loft_visible_history_entities(document, handle) {
// Call the raw converter, not this wrapper: sources must not expand
// the parent's history again through their shared selection handle.
let mut construction = if let EntityType::Region(region) = &source {
// Regions normally render through the mesh layer, so the raw
// wire converter emits nothing. Decode every exact boundary,
// including holes, then sample only for this cached display.
let mut boundaries = Vec::new();
if let Ok(cadkernel::brep::LoftSection::Profile { plane, wires, .. }) =
cadkernel::acis::loft_section_geometry(&[
acadrust::entities::EmbeddedEntity::Region(region.clone()),
])
{
let (color, _, _, line_weight_px, _) =
view::render::render_style_for_viewport(document, &source, active_viewport);
for boundary in wires {
for curve in boundary {
let points = curve.tessellate(64.0 / std::f64::consts::TAU)
.into_iter().map(|point| plane.point_at(point));
let (points, points_low) = convert::tessellate::points_to_ds(points);
let mut wire = WireModel::solid(
handle.value().to_string(), points, color, selected.contains(&handle),
);
wire.points_low = points_low;
wire.line_weight_px = line_weight_px;
boundaries.push(wire);
}
}
}
boundaries
} else {
convert::tess::tessellate_entity(
document, selected, active_viewport, bg_color, anno_scale,
annotation_scale_handle, &source, block_cache, view_aabb,
world_per_pixel, paper_space,
)
};
for wire in &mut construction {
wire.name = handle.value().to_string();
if !wire.selected {
wire.color = [1.0, 0.25, 0.25, wire.color[3]];
}
wire.fill_tris.clear();
wire.fill_tris_low.clear();
}
wires.extend(construction);
}
}
wires
}
/// Result of `Scene::entity_index()`. The wire path queries `tree` for
/// view-rect candidates and also always emits `unbounded_handles`
/// (entities with no usable bbox — legacy `UNBOUNDED_AABB` sentinel).
@@ -2731,12 +2805,12 @@ impl Scene {
})
}
/// Cache and display a Model-tab kernel solid.
pub fn register_solid_model(
&mut self,
/// Prepare display geometry without changing the entity or resident caches.
pub(crate) fn prepare_solid_model_display(
&self,
handle: Handle,
solid: cadkernel::brep::Body,
) -> bool {
solid: &cadkernel::brep::Body,
) -> Option<(MeshLodSet, Vec<acadrust::entities::Wire>, [f64; 3])> {
let color = self
.document
.get_entity(handle)
@@ -2746,67 +2820,88 @@ impl Scene {
let chordal_deflection =
crate::entities::solid3d::display_deflection(&self.document.header, facet_resolution);
let isolines = self.document.header.isolines.max(0) as usize;
let displayed = if let Some((mut set, wires, center)) =
let (mut set, wires, center) =
crate::scene::model::solid_model::display_from_solid(
&solid,
solid,
color,
facet_resolution,
chordal_deflection,
isolines,
)?;
let name = handle.value().to_string();
for mesh in &mut set.lods {
mesh.name = name.clone();
}
if let Some(entity) = self.document.get_entity(handle) {
crate::scene::model::material_model::resolve_material_with_base(
&self.document,
entity,
color,
None,
self.material_base_dir.as_deref(),
)
{
if let Some(entity) = self.document.get_entity_mut(handle) {
let reference = acadrust::types::Vector3::new(center[0], center[1], center[2]);
match entity {
EntityType::Solid3D(entity) => {
entity.point_of_reference = reference;
entity.wires = wires;
entity.silhouettes.clear();
}
EntityType::Surface(entity) => {
entity.point_of_reference = reference;
entity.wires = wires;
entity.silhouettes.clear();
}
_ => {}
.apply_to_with_face_overrides(
&mut set,
&self.document,
self.material_base_dir.as_deref(),
);
crate::scene::model::visual_style_model::apply_mesh_visual_style(
&mut set,
&self.document,
entity,
);
}
Some((set, wires, center))
}
/// Commit a prepared display without tessellating the body a second time.
pub(crate) fn register_prepared_solid_model(
&mut self,
handle: Handle,
solid: cadkernel::brep::Body,
display: (MeshLodSet, Vec<acadrust::entities::Wire>, [f64; 3]),
) -> bool {
let (set, wires, center) = display;
if let Some(entity) = self.document.get_entity_mut(handle) {
let reference = acadrust::types::Vector3::new(center[0], center[1], center[2]);
match entity {
EntityType::Solid3D(entity) => {
entity.point_of_reference = reference;
entity.wires = wires;
entity.silhouettes.clear();
}
EntityType::Surface(entity) => {
entity.point_of_reference = reference;
entity.wires = wires;
entity.silhouettes.clear();
}
_ => {}
}
let name = handle.value().to_string();
for mesh in &mut set.lods {
mesh.name = name.clone();
}
if let Some(entity) = self.document.get_entity(handle) {
crate::scene::model::material_model::resolve_material_with_base(
&self.document,
entity,
color,
None,
self.material_base_dir.as_deref(),
)
.apply_to_with_face_overrides(
&mut set,
&self.document,
self.material_base_dir.as_deref(),
);
crate::scene::model::visual_style_model::apply_mesh_visual_style(
&mut set,
&self.document,
entity,
);
}
self.meshes.insert(handle, set);
true
} else {
self.meshes.remove(&handle);
false
};
}
self.meshes.insert(handle, set);
self.solid_models.insert(handle, solid);
self.sync_solid_reference_point(handle);
// Only this solid's mesh changed — report just its handle so the mesh /
// wire caches patch it in rather than rebuilding the whole drawing (and
// so a bulk register loop stays O(n), not O(n²)).
self.bump_entities(&[(handle, ChangeKind::Modified)]);
displayed
true
}
/// Cache and display a Model-tab kernel solid. A failed preparation leaves
/// the previous entity, body and mesh untouched.
pub fn register_solid_model(
&mut self,
handle: Handle,
solid: cadkernel::brep::Body,
) -> bool {
let Some(display) = self.prepare_solid_model_display(handle, &solid) else {
return false;
};
if !display.0.complete && matches!(
self.document.solid_history_operation(handle),
Some(acadrust::objects::SolidHistoryOperation::Loft(_))
) {
return false;
}
self.register_prepared_solid_model(handle, solid, display)
}
/// `BACKGROUND` change picks up the new `adapt_to_bg` result without
+95
View File
@@ -0,0 +1,95 @@
//! LOFT command/history integration. Geometry and validation live in the kernel.
use acadrust::entities::{EmbeddedEntity, Point, Surface, SurfaceData, SurfaceKind};
use acadrust::objects::{SolidHistoryLoft, SolidHistoryLoftParameters, SolidHistoryNodeBase};
use acadrust::{EntityType, Handle};
use cadkernel::brep::Body;
use crate::command::{ExtrudeMode, LoftOptions, LoftSectionSelection};
use super::sweep_model::embedded_path;
fn embedded_section(entity: &EntityType) -> Option<EmbeddedEntity> {
match entity {
EntityType::Region(region) => Some(EmbeddedEntity::Region(region.clone())),
EntityType::Point(point) => Some(EmbeddedEntity::Point(point.clone())),
_ => embedded_path(entity),
}
}
pub fn is_section(entity: &EntityType) -> bool {
embedded_section(entity).is_some_and(|entity|
cadkernel::acis::loft_section_geometry(&[entity]).is_ok())
}
pub fn is_guide_or_path(entity: &EntityType) -> bool {
embedded_path(entity).is_some_and(|entity| cadkernel::acis::loft_path_geometry(&entity).is_ok())
}
pub fn record(
sections: &[LoftSectionSelection], guides: &[Handle], path: Option<Handle>,
available: &[(Handle, EntityType)], mode: ExtrudeMode, options: LoftOptions,
) -> Result<SolidHistoryLoft, String> {
let find = |handle| available.iter().find(|(candidate, _)| *candidate == handle)
.map(|(_, entity)| entity).ok_or_else(|| "A selected loft source no longer exists.".to_string());
let mut cross_sections = Vec::new();
let mut section_counts = Vec::new();
let mut surface = mode == ExtrudeMode::Surface;
for section in sections {
let members = match section {
LoftSectionSelection::Entity(handle) => vec![embedded_section(find(*handle)?)
.ok_or("Unsupported loft section.")?],
LoftSectionSelection::Point(point) => vec![EmbeddedEntity::Point(Point::from_coords(point.x, point.y, point.z))],
LoftSectionSelection::Join(handles) => handles.iter().map(|handle| {
embedded_section(find(*handle)?).ok_or_else(|| "Unsupported joined loft edge.".to_string())
}).collect::<Result<Vec<_>, _>>()?,
};
if let cadkernel::brep::LoftSection::Profile { closed, .. } = cadkernel::acis::loft_section_geometry(&members)? {
surface |= !closed;
}
section_counts.push(members.len());
cross_sections.extend(members);
}
let guides = guides.iter().map(|handle| embedded_path(find(*handle)?)
.ok_or_else(|| "Unsupported loft guide.".to_string())).collect::<Result<Vec<_>, _>>()?;
let path_entity = path.map(|handle| embedded_path(find(handle)?)
.ok_or_else(|| "Unsupported loft path.".to_string())).transpose()?;
Ok(SolidHistoryLoft {
base: SolidHistoryNodeBase::new(1), operation_major: 1,
cross_sections, guides,
parameters: Some(SolidHistoryLoftParameters {
path_entity, normals: options.normals,
start_draft_angle: options.start_draft_angle, end_draft_angle: options.end_draft_angle,
start_magnitude: options.start_magnitude, end_magnitude: options.end_magnitude,
start_continuity: options.start_continuity, end_continuity: options.end_continuity,
start_bulge: options.start_bulge, end_bulge: options.end_bulge,
closed: options.closed, periodic: options.periodic, surface, align_direction: options.align_direction, section_counts,
}),
..SolidHistoryLoft::default()
})
}
pub fn build_body(
sections: &[LoftSectionSelection], guides: &[Handle], path: Option<Handle>,
available: &[(Handle, EntityType)], mode: ExtrudeMode, options: LoftOptions,
) -> Result<Body, String> {
cadkernel::acis::rebuild_loft_with_options(&record(sections, guides, path, available, mode, options)?)
}
pub fn surface_entity(record: &SolidHistoryLoft) -> EntityType {
let settings = record.parameters.clone().unwrap_or_else(|| SolidHistoryLoftParameters {
normals: 0, ..Default::default()
});
let mut surface = Surface::new(SurfaceKind::Lofted);
surface.surface_data = SurfaceData::Lofted {
loft_transform: record.base.transform,
cross_section_entities: record.cross_sections.clone(), guide_entities: record.guides.clone(),
path_entity: settings.path_entity,
plane_normal_lofting_type: settings.normals,
start_draft_angle: settings.start_draft_angle, end_draft_angle: settings.end_draft_angle,
start_draft_magnitude: settings.start_magnitude, end_draft_magnitude: settings.end_magnitude,
arc_length_parameterization: true, no_twist: true, align_direction: settings.align_direction,
simple_surfaces: true, closed_surfaces: settings.closed, solid: false,
ruled_surface: settings.normals == 0, virtual_guide: false,
cross_sections: Vec::new(), guide_curves: Vec::new(), path_curve: None,
};
EntityType::Surface(surface)
}
+1
View File
@@ -11,6 +11,7 @@ pub mod mesh_model;
pub mod solid_model;
pub mod solid_history;
pub mod sweep_model;
pub mod loft_command_model;
mod sweep_command_model;
pub mod visual_style_model;
pub mod object;
+309 -2
View File
@@ -1,7 +1,7 @@
use acadrust::entities::{EmbeddedEntity, Solid3D};
use acadrust::objects::{
DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep, SolidHistoryCone,
SolidHistoryCylinder, SolidHistoryLoft, SolidHistoryNodeBase, SolidHistoryOperation,
SolidHistoryCylinder, SolidHistoryLoft, SolidHistoryLoftParameters, SolidHistoryNodeBase, SolidHistoryOperation,
SolidHistoryPyramid, SolidHistoryRevolve, SolidHistorySphere, SolidHistorySweep,
SolidHistoryTorus,
};
@@ -76,6 +76,15 @@ pub const PROP_AXIS_DIRECTION_X: &str = "solid_history_axis_direction_x";
pub const PROP_AXIS_DIRECTION_Y: &str = "solid_history_axis_direction_y";
pub const PROP_AXIS_DIRECTION_Z: &str = "solid_history_axis_direction_z";
pub const PROP_PYRAMID_TYPE: &str = "solid_history_pyramid_type";
pub const PROP_LOFT_TYPE: &str = "solid_history_loft_type";
pub const PROP_LOFT_SECTION_COUNT: &str = "solid_history_loft_section_count";
pub const PROP_LOFT_NORMALS: &str = "solid_history_loft_normals";
pub const PROP_LOFT_START_DRAFT_ANGLE: &str = "solid_history_loft_start_draft_angle";
pub const PROP_LOFT_END_DRAFT_ANGLE: &str = "solid_history_loft_end_draft_angle";
pub const PROP_LOFT_START_MAGNITUDE: &str = "solid_history_loft_start_magnitude";
pub const PROP_LOFT_END_MAGNITUDE: &str = "solid_history_loft_end_magnitude";
pub const PROP_LOFT_CLOSED: &str = "solid_history_loft_closed";
pub const PROP_LOFT_PERIODIC: &str = "solid_history_loft_periodic";
pub const PROP_HISTORY: &str = "solid_history_record";
pub const PROP_SHOW_HISTORY: &str = "solid_history_show";
@@ -195,6 +204,7 @@ pub fn has_specialized_primitive_properties(
| SolidHistoryOperation::Torus(_)
| SolidHistoryOperation::Pyramid(_)
| SolidHistoryOperation::Sweep(_)
| SolidHistoryOperation::Loft(_)
| SolidHistoryOperation::Extrusion(_)
| SolidHistoryOperation::Revolve(_)
)
@@ -519,6 +529,161 @@ fn sweep_properties(
]
}
const LOFT_NORMAL_CHOICES: [&str; 7] = [
"Ruled",
"Smooth",
"First normal",
"Last normal",
"Ends normal",
"All normal",
"Use draft angles",
];
fn loft_parameters(value: &SolidHistoryLoft) -> SolidHistoryLoftParameters {
value.parameters.clone().unwrap_or_else(|| SolidHistoryLoftParameters {
// Older history predates the options extension and rebuilds as ruled.
// Displaying or editing it must not silently change it to smooth.
normals: 0,
..SolidHistoryLoftParameters::default()
})
}
pub fn loft_section_count(value: &SolidHistoryLoft) -> usize {
value.parameters.as_ref()
.filter(|parameters| !parameters.section_counts.is_empty())
.map_or(value.cross_sections.len(), |parameters| parameters.section_counts.len())
}
pub fn loft_closed_editable(value: &SolidHistoryLoft) -> bool {
loft_section_count(value) >= 3
&& !value.cross_sections.iter().any(|section| matches!(section, EmbeddedEntity::Point(_)))
}
fn loft_properties(
document: &acadrust::CadDocument,
handle: acadrust::Handle,
value: &SolidHistoryLoft,
) -> Vec<PropSection> {
let parameters = loft_parameters(value);
let kind = if parameters.path_entity.is_some() {
t!("Loft with path").into_owned()
} else if !value.guides.is_empty() {
t!("Loft with guides").into_owned()
} else {
t!("Loft with cross sections only").into_owned()
};
let mut geometry = vec![
Property {
label: t!("Solid type").into_owned(),
field: PROP_LOFT_TYPE,
value: PropValue::ReadOnly(kind),
},
Property {
label: t!("Number of Cross sections").into_owned(),
field: PROP_LOFT_SECTION_COUNT,
value: PropValue::ReadOnly(loft_section_count(value).to_string()),
},
Property {
label: t!("Surface Normals").into_owned(),
field: PROP_LOFT_NORMALS,
value: PropValue::Choice {
selected: LOFT_NORMAL_CHOICES.get(parameters.normals as usize)
.map_or_else(|| parameters.normals.to_string(), |name| (*name).to_string()),
options: LOFT_NORMAL_CHOICES.iter().map(|name| (*name).to_string()).collect(),
},
},
];
if parameters.normals == 6 {
geometry.extend([
Property {
label: t!("Start draft angle").into_owned(),
field: PROP_LOFT_START_DRAFT_ANGLE,
value: PropValue::EditText(crate::entities::common::format_angle(parameters.start_draft_angle)),
},
Property {
label: t!("End draft angle").into_owned(),
field: PROP_LOFT_END_DRAFT_ANGLE,
value: PropValue::EditText(crate::entities::common::format_angle(parameters.end_draft_angle)),
},
Property {
label: t!("Start magnitude").into_owned(),
field: PROP_LOFT_START_MAGNITUDE,
value: PropValue::EditText(parameters.start_magnitude.to_string()),
},
Property {
label: t!("End magnitude").into_owned(),
field: PROP_LOFT_END_MAGNITUDE,
value: PropValue::EditText(parameters.end_magnitude.to_string()),
},
]);
}
let (record_history, object_show_history, show_history_mode) =
history_flags(document, handle).unwrap_or((false, false, 1));
let (show_history, show_history_editable) =
displayed_history_state(object_show_history, show_history_mode);
let show_value = if show_history { "Yes" } else { "No" }.to_string();
let closed_value = if parameters.closed { "Yes" } else { "No" }.to_string();
let mut sections = vec![
PropSection {
title: t!("Geometry").into_owned(),
props: geometry,
},
PropSection {
title: t!("Solid History").into_owned(),
props: vec![
Property {
label: t!("History").into_owned(),
field: PROP_HISTORY,
value: PropValue::Choice {
selected: if record_history { "Record" } else { "None" }.to_string(),
options: vec!["Record".to_string(), "None".to_string()],
},
},
Property {
label: t!("Show History").into_owned(),
field: PROP_SHOW_HISTORY,
value: if show_history_editable {
PropValue::Choice {
selected: show_value,
options: vec!["Yes".to_string(), "No".to_string()],
}
} else {
PropValue::ReadOnly(show_value)
},
},
],
},
PropSection {
title: t!("Misc").into_owned(),
props: vec![
Property {
label: t!("Closed").into_owned(),
field: PROP_LOFT_CLOSED,
value: if loft_closed_editable(value) {
PropValue::Choice {
selected: closed_value,
options: vec!["Yes".to_string(), "No".to_string()],
}
} else {
PropValue::ReadOnly(closed_value)
},
},
],
},
];
if parameters.closed {
sections[2].props.push(Property {
label: t!("Periodic").into_owned(),
field: PROP_LOFT_PERIODIC,
value: PropValue::Choice {
selected: if parameters.periodic { "Yes" } else { "No" }.to_string(),
options: vec!["Yes".to_string(), "No".to_string()],
},
});
}
sections
}
fn sweep_path_length(value: &SolidHistorySweep) -> Option<f64> {
use acadrust::entities::EmbeddedEntity;
use acadrust::EntityType;
@@ -1209,6 +1374,7 @@ pub fn primitive_properties(
SolidHistoryOperation::Pyramid(value) => pyramid_properties(document, handle, value),
SolidHistoryOperation::Torus(value) => torus_properties(document, handle, value),
SolidHistoryOperation::Sweep(value) => sweep_properties(document, handle, value),
SolidHistoryOperation::Loft(value) => loft_properties(document, handle, value),
SolidHistoryOperation::Extrusion(value) => {
extrusion_properties(document, handle, value)
}
@@ -1253,6 +1419,13 @@ pub fn is_primitive_property(field: &str) -> bool {
| PROP_AXIS_DIRECTION_Y
| PROP_AXIS_DIRECTION_Z
| PROP_PYRAMID_TYPE
| PROP_LOFT_NORMALS
| PROP_LOFT_START_DRAFT_ANGLE
| PROP_LOFT_END_DRAFT_ANGLE
| PROP_LOFT_START_MAGNITUDE
| PROP_LOFT_END_MAGNITUDE
| PROP_LOFT_CLOSED
| PROP_LOFT_PERIODIC
)
}
@@ -1260,8 +1433,13 @@ pub fn is_history_choice(field: &str) -> bool {
matches!(field, PROP_HISTORY | PROP_SHOW_HISTORY)
}
pub fn is_loft_geometry_choice(field: &str) -> bool {
matches!(field, PROP_LOFT_NORMALS | PROP_LOFT_CLOSED | PROP_LOFT_PERIODIC)
}
pub fn is_specialized_property(field: &str) -> bool {
matches!(field, "solid_history_type" | PROP_BANK | PROP_SWEEP_LENGTH)
matches!(field, "solid_history_type" | PROP_BANK | PROP_SWEEP_LENGTH
| PROP_LOFT_TYPE | PROP_LOFT_SECTION_COUNT)
|| is_primitive_property(field)
|| is_history_choice(field)
}
@@ -1688,6 +1866,85 @@ fn apply_sweep_geometry_property(
}
}
fn apply_loft_geometry_property(
value: &mut SolidHistoryLoft,
field: &str,
text: &str,
) -> Option<bool> {
let current = loft_parameters(value);
let mut parameters = current.clone();
match field {
PROP_LOFT_NORMALS => {
let Some(index) = LOFT_NORMAL_CHOICES.iter()
.position(|name| name.eq_ignore_ascii_case(text.trim()))
else {
return Some(false);
};
parameters.normals = index as i32;
}
PROP_LOFT_START_DRAFT_ANGLE | PROP_LOFT_END_DRAFT_ANGLE => {
if parameters.normals != 6 {
return Some(false);
}
let Some(angle) = crate::entities::common::parse_angle(text)
.filter(|angle| angle.is_finite() && (0.0..=std::f64::consts::PI).contains(angle))
else {
return Some(false);
};
if field == PROP_LOFT_START_DRAFT_ANGLE {
parameters.start_draft_angle = angle;
} else {
parameters.end_draft_angle = angle;
}
}
PROP_LOFT_START_MAGNITUDE | PROP_LOFT_END_MAGNITUDE => {
if parameters.normals != 6 {
return Some(false);
}
let Some(magnitude) = text.trim().replace(',', ".").parse::<f64>().ok()
.filter(|magnitude| magnitude.is_finite() && *magnitude >= 0.0)
else {
return Some(false);
};
if field == PROP_LOFT_START_MAGNITUDE {
parameters.start_magnitude = magnitude;
} else {
parameters.end_magnitude = magnitude;
}
}
PROP_LOFT_CLOSED => {
if !loft_closed_editable(value) {
return Some(false);
}
parameters.closed = if text.trim().eq_ignore_ascii_case("Yes") {
true
} else if text.trim().eq_ignore_ascii_case("No") {
false
} else {
return Some(false);
};
}
PROP_LOFT_PERIODIC => {
if !parameters.closed {
return Some(false);
}
parameters.periodic = if text.trim().eq_ignore_ascii_case("Yes") {
true
} else if text.trim().eq_ignore_ascii_case("No") {
false
} else {
return Some(false);
};
}
_ => return None,
}
if parameters == current {
return Some(false);
}
value.parameters = Some(parameters);
Some(true)
}
fn apply_torus_position_property(
value: &mut SolidHistoryTorus,
field: &str,
@@ -1881,6 +2138,11 @@ pub fn apply_primitive_property(
return applied;
}
}
if let SolidHistoryOperation::Loft(loft_value) = operation {
if let Some(applied) = apply_loft_geometry_property(loft_value, field, value) {
return applied;
}
}
if let SolidHistoryOperation::Extrusion(extrusion_value) = operation {
if let Some(applied) =
apply_extrusion_geometry_property(extrusion_value, field, value)
@@ -2188,6 +2450,51 @@ fn embedded_entity(value: &EmbeddedEntity) -> Option<EntityType> {
})
}
/// Visible LOFT construction curves, owned by the parent display entity.
/// Copies are transformed to WCS and never inserted into the document.
pub fn loft_visible_history_entities(
document: &acadrust::CadDocument,
handle: acadrust::Handle,
) -> Vec<EntityType> {
let Some((_, object_show_history, show_history_mode)) = history_flags(document, handle) else {
return Vec::new();
};
if !displayed_history_state(object_show_history, show_history_mode).0 {
return Vec::new();
}
let Some(SolidHistoryOperation::Loft(value)) = document.solid_history_operation(handle) else {
return Vec::new();
};
let Some(parent) = document.get_entity(handle) else {
return Vec::new();
};
let Some(transform) = matrix(value.base.transform) else {
return Vec::new();
};
let columns = transform.to_cols_array_2d();
let affine = acadrust::types::Transform::from_matrix(
acadrust::types::Matrix4 {
m: std::array::from_fn(|row| std::array::from_fn(|column| columns[column][row])),
},
);
value.cross_sections.iter()
.chain(&value.guides)
.chain(value.parameters.as_ref().and_then(|parameters| parameters.path_entity.as_ref()))
.filter_map(|source| {
let mut entity = embedded_entity(source)?;
entity.apply_transform(&affine);
// Construction sources belong to the visible parent's layer and
// selection identity, not a deleted source or hidden source layer.
let common = entity.common_mut();
common.handle = handle;
common.owner_handle = parent.common().owner_handle;
common.layer = parent.common().layer.clone();
common.invisible = parent.common().invisible;
Some(entity)
})
.collect()
}
fn into_embedded_entity(value: EntityType) -> Option<EmbeddedEntity> {
Some(match value {
EntityType::Point(value) => EmbeddedEntity::Point(value),
+54 -15
View File
@@ -841,13 +841,29 @@ impl Scene {
handle: Handle,
operation: acadrust::objects::SolidHistoryOperation,
) -> bool {
self.solid_history_preview_wires.remove(&handle);
let Ok(body) = cadkernel::acis::rebuild_body(&operation) else {
return false;
};
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body) else {
return false;
};
let loft_surface_data = match (&operation, self.document.get_entity(handle)) {
(acadrust::objects::SolidHistoryOperation::Loft(record), Some(EntityType::Surface(_))) => {
let EntityType::Surface(surface) = crate::scene::model::loft_command_model::surface_entity(record)
else { return false; };
Some(surface.surface_data)
}
(_, Some(EntityType::Solid3D(_))) => None,
_ => return false,
};
let Some(display) = self.prepare_solid_model_display(handle, &body) else {
return false;
};
if matches!(&operation, acadrust::objects::SolidHistoryOperation::Loft(_))
&& !display.0.complete
{
return false;
}
self.record_solid_history_before(handle);
if self
.document
@@ -856,12 +872,16 @@ impl Scene {
{
return false;
}
let Some(EntityType::Solid3D(entity)) = self.document.get_entity_mut(handle) else {
return false;
};
entity.set_sat_document(&document);
self.sync_solid_reference_point(handle);
self.register_solid_model(handle, body);
match self.document.get_entity_mut(handle) {
Some(EntityType::Solid3D(entity)) => entity.set_sat_document(&document),
Some(EntityType::Surface(entity)) => {
entity.acis_data = acadrust::entities::AcisData::from_sat(&document.to_sat_string());
if let Some(data) = loft_surface_data { entity.surface_data = data; }
}
_ => return false,
}
self.register_prepared_solid_model(handle, body, display);
self.solid_history_preview_wires.remove(&handle);
true
}
@@ -875,13 +895,28 @@ impl Scene {
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body) else {
return false;
};
let Some(EntityType::Solid3D(entity)) = self.document.get_entity_mut(handle) else {
let Some(display) = self.prepare_solid_model_display(handle, &body) else {
return false;
};
entity.set_sat_document(&document);
self.sync_solid_reference_point(handle);
if matches!(&operation, acadrust::objects::SolidHistoryOperation::Loft(_))
&& !display.0.complete
{
return false;
}
match self.document.get_entity_mut(handle) {
Some(EntityType::Solid3D(entity)) => entity.set_sat_document(&document),
Some(EntityType::Surface(entity)) => {
entity.acis_data = acadrust::entities::AcisData::from_sat(&document.to_sat_string());
if let acadrust::objects::SolidHistoryOperation::Loft(record) = &operation {
if let EntityType::Surface(updated) = crate::scene::model::loft_command_model::surface_entity(record) {
entity.surface_data = updated.surface_data;
}
}
}
_ => return false,
}
self.register_prepared_solid_model(handle, body, display);
self.solid_history_preview_wires.remove(&handle);
self.register_solid_model(handle, body);
true
}
@@ -900,9 +935,9 @@ impl Scene {
{
return false;
}
let Some(EntityType::Solid3D(_)) = self.document.get_entity(handle) else {
if !matches!(self.document.get_entity(handle), Some(EntityType::Solid3D(_) | EntityType::Surface(_))) {
return false;
};
}
self.solid_history_preview_wires.insert(
handle,
crate::scene::model::solid_model::grip_preview_wires(&body, handle),
@@ -945,12 +980,16 @@ impl Scene {
value: &str,
) -> bool {
self.record_solid_history_before(handle);
crate::scene::model::solid_history::apply_history_choice(
let applied = crate::scene::model::solid_history::apply_history_choice(
&mut self.document,
handle,
field,
value,
)
);
if applied {
self.bump_entities(&[(handle, ChangeKind::Modified)]);
}
applied
}
fn apply_solid_history_grip(