Files
OneCAD-Draft/src/entities/object_data.rs
T
Hakan Seven 4a30928f5a fix(ui): refine empty drawing state
Show drawing defaults when nothing is selected and disable layout controls on Start.

Refs #514
Refs #515
2026-07-27 17:37:17 +03:00

2722 lines
110 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! OCS-facing integration for non-graphical DWG object graphs.
//!
//! Dynamic-block evaluation records, solid construction history and
//! associative networks do not draw standalone geometry. Their native place in
//! OCS is the selected entity's semantic/property view, while the evaluated
//! entity geometry continues through the normal render pipeline.
use acadrust::objects::{
AssociativeData, BlockEvalValue, DynamicBlockData, ObjectType,
SolidHistoryNodeBase, SolidHistoryOperation,
};
use acadrust::{CadDocument, EntityType, Handle};
use crate::scene::model::object::{PropSection, PropValue, Property};
#[derive(Debug, Clone, Default)]
pub struct ObjectDataCache {
document_sections: std::sync::Arc<Vec<PropSection>>,
dynamic_objects: std::sync::Arc<Vec<Handle>>,
associative_objects: std::sync::Arc<Vec<Handle>>,
light_entities: std::sync::Arc<Vec<Handle>>,
sun_objects: std::sync::Arc<Vec<Handle>>,
geo_objects: std::sync::Arc<Vec<Handle>>,
}
pub fn build_cache(document: &CadDocument) -> ObjectDataCache {
let mut dynamic_objects = Vec::new();
let mut associative_objects = Vec::new();
let mut ordered_lights = Vec::new();
let mut sun_objects = Vec::new();
let mut geo_objects = Vec::new();
for (handle, object) in &document.objects {
match object {
ObjectType::DynamicBlock(_) => dynamic_objects.push(*handle),
ObjectType::Associative(_) => associative_objects.push(*handle),
ObjectType::GeoData(_) => geo_objects.push(*handle),
ObjectType::ClassObject(value) => match &value.data {
acadrust::objects::ClassObjectData::LightList(list) => {
ordered_lights.extend(list.lights.iter().map(|entry| entry.handle));
}
acadrust::objects::ClassObjectData::Sun(_) => sun_objects.push(*handle),
_ => {}
},
_ => {}
}
}
let mut seen_lights = rustc_hash::FxHashSet::default();
ordered_lights.retain(|handle| seen_lights.insert(*handle));
for entity in document.entities() {
if let EntityType::Light(light) = entity {
if seen_lights.insert(light.common.handle) {
ordered_lights.push(light.common.handle);
}
}
}
dynamic_objects.sort_by_key(|handle| handle.value());
associative_objects.sort_by_key(|handle| handle.value());
sun_objects.sort_by_key(|handle| handle.value());
geo_objects.sort_by_key(|handle| handle.value());
ObjectDataCache {
document_sections: std::sync::Arc::new(build_document_sections(document)),
dynamic_objects: std::sync::Arc::new(dynamic_objects),
associative_objects: std::sync::Arc::new(associative_objects),
light_entities: std::sync::Arc::new(ordered_lights),
sun_objects: std::sync::Arc::new(sun_objects),
geo_objects: std::sync::Arc::new(geo_objects),
}
}
pub fn cache_is_prepared(cache: &ObjectDataCache) -> bool {
!cache.document_sections.is_empty()
}
pub fn light_entities(cache: &ObjectDataCache) -> &[Handle] {
&cache.light_entities
}
pub fn sun_objects(cache: &ObjectDataCache) -> &[Handle] {
&cache.sun_objects
}
pub fn geo_objects(cache: &ObjectDataCache) -> &[Handle] {
&cache.geo_objects
}
pub fn update_light_entity(
cache: &mut ObjectDataCache,
handle: Handle,
exists: bool,
) {
let lights = std::sync::Arc::make_mut(&mut cache.light_entities);
if exists {
if !lights.contains(&handle) {
lights.push(handle);
}
} else {
lights.retain(|candidate| *candidate != handle);
}
}
fn ro(label: impl Into<String>, value: impl Into<String>) -> Property {
Property {
label: label.into(),
field: "dwg_object_data",
value: PropValue::ReadOnly(value.into()),
}
}
fn handle_text(handle: Handle) -> String {
if handle.is_valid() {
format!("{:X}", handle.value())
} else {
"None".to_string()
}
}
fn entity_history_handle(entity: &EntityType) -> Option<Handle> {
match entity {
EntityType::Solid3D(value) => value.history_handle,
EntityType::Region(value) => value.history_handle,
EntityType::Body(value) => value.history_handle,
EntityType::Surface(value) => value.history_handle,
_ => None,
}
.filter(|handle| handle.is_valid())
}
fn history_operation_name(operation: &SolidHistoryOperation) -> &'static str {
match operation {
SolidHistoryOperation::Unknown => "Unknown",
SolidHistoryOperation::Box(_) => "Box",
SolidHistoryOperation::Wedge(_) => "Wedge",
SolidHistoryOperation::Sphere(_) => "Sphere",
SolidHistoryOperation::Cylinder(_) => "Cylinder",
SolidHistoryOperation::Cone(_) => "Cone",
SolidHistoryOperation::Pyramid(_) => "Pyramid",
SolidHistoryOperation::Torus(_) => "Torus",
SolidHistoryOperation::Boolean(_) => "Boolean",
SolidHistoryOperation::Brep(_) => "Brep",
SolidHistoryOperation::Fillet(_) => "Fillet",
SolidHistoryOperation::Chamfer(_) => "Chamfer",
SolidHistoryOperation::Sweep(_) => "Sweep",
SolidHistoryOperation::Extrusion(_) => "Extrusion",
SolidHistoryOperation::Loft(_) => "Loft",
SolidHistoryOperation::Revolve(_) => "Revolve",
}
}
fn history_step_id(operation: &SolidHistoryOperation) -> Option<i32> {
let base = match operation {
SolidHistoryOperation::Unknown => return None,
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => &value.base,
SolidHistoryOperation::Sphere(value) => &value.base,
SolidHistoryOperation::Cylinder(value) | SolidHistoryOperation::Cone(value) => &value.base,
SolidHistoryOperation::Pyramid(value) => &value.base,
SolidHistoryOperation::Torus(value) => &value.base,
SolidHistoryOperation::Boolean(value) => &value.base,
SolidHistoryOperation::Brep(value) => &value.base,
SolidHistoryOperation::Fillet(value) => &value.base,
SolidHistoryOperation::Chamfer(value) => &value.base,
SolidHistoryOperation::Sweep(value) | SolidHistoryOperation::Extrusion(value) => {
&value.base
}
SolidHistoryOperation::Loft(value) => &value.base,
SolidHistoryOperation::Revolve(value) => &value.base,
};
Some(base.step_id)
}
fn history_base_text(base: &SolidHistoryNodeBase) -> String {
format!(
"node {}; version {}.{}; material {}; color {:?}; transform [{:.4}, {:.4}, {:.4}, {:.4}; {:.4}, {:.4}, {:.4}, {:.4}; {:.4}, {:.4}, {:.4}, {:.4}; {:.4}, {:.4}, {:.4}, {:.4}]",
base.eval.node_id,
base.major,
base.minor,
handle_text(base.material),
base.color,
base.transform[0],
base.transform[1],
base.transform[2],
base.transform[3],
base.transform[4],
base.transform[5],
base.transform[6],
base.transform[7],
base.transform[8],
base.transform[9],
base.transform[10],
base.transform[11],
base.transform[12],
base.transform[13],
base.transform[14],
base.transform[15],
)
}
fn embedded_name(value: Option<&acadrust::entities::EmbeddedEntity>) -> &'static str {
match value {
Some(acadrust::entities::EmbeddedEntity::Point(_)) => "Point",
Some(acadrust::entities::EmbeddedEntity::Line(_)) => "Line",
Some(acadrust::entities::EmbeddedEntity::Arc(_)) => "Arc",
Some(acadrust::entities::EmbeddedEntity::Circle(_)) => "Circle",
Some(acadrust::entities::EmbeddedEntity::Ellipse(_)) => "Ellipse",
Some(acadrust::entities::EmbeddedEntity::Spline(_)) => "Spline",
Some(acadrust::entities::EmbeddedEntity::LwPolyline(_)) => "Polyline",
Some(acadrust::entities::EmbeddedEntity::Ray(_)) => "Ray",
Some(acadrust::entities::EmbeddedEntity::XLine(_)) => "XLine",
Some(acadrust::entities::EmbeddedEntity::Unknown { .. }) => "Unknown",
None => "None",
}
}
fn history_operation_text(operation: &SolidHistoryOperation) -> String {
match operation {
SolidHistoryOperation::Unknown => "Unknown operation".to_string(),
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => format!(
"{}; length {:.6}; width {:.6}; height {:.6}",
history_base_text(&value.base),
value.length,
value.width,
value.height
),
SolidHistoryOperation::Sphere(value) => format!(
"{}; radius {:.6}",
history_base_text(&value.base),
value.radius
),
SolidHistoryOperation::Cylinder(value) | SolidHistoryOperation::Cone(value) => format!(
"{}; height {:.6}; radii {:.6}/{:.6}; x-radius {:.6}",
history_base_text(&value.base),
value.height,
value.major_radius,
value.minor_radius,
value.x_radius
),
SolidHistoryOperation::Pyramid(value) => format!(
"{}; height {:.6}; sides {}; radii {:.6}/{:.6}",
history_base_text(&value.base),
value.height,
value.sides,
value.radius,
value.top_radius
),
SolidHistoryOperation::Torus(value) => format!(
"{}; radii {:.6}/{:.6}",
history_base_text(&value.base),
value.major_radius,
value.minor_radius
),
SolidHistoryOperation::Boolean(value) => format!(
"{}; operation {}; operands {}/{}",
history_base_text(&value.base),
value.operation,
value.first_operand,
value.second_operand
),
SolidHistoryOperation::Brep(value) => format!(
"{}; ACIS {} bytes; materials {}",
history_base_text(&value.base),
value.acis_data.sat_data.len() + value.acis_data.sab_data.len(),
value.acis_data.materials.len()
),
SolidHistoryOperation::Fillet(value) => format!(
"{}; method {}; edges {}; radii {}; setbacks {}/{}",
history_base_text(&value.base),
value.method,
value.edges.len(),
value.radii.len(),
value.start_setbacks.len(),
value.end_setbacks.len()
),
SolidHistoryOperation::Chamfer(value) => format!(
"{}; method {}; distances {:.6}/{:.6}; edges {}; base face {}",
history_base_text(&value.base),
value.method,
value.base_distance,
value.other_distance,
value.edges.len(),
value.base_face
),
SolidHistoryOperation::Sweep(value) | SolidHistoryOperation::Extrusion(value) => format!(
"{}; sweep {}; path {}; direction {:.6},{:.6},{:.6}; draft {:.6}; scale {:.6}; twist {:.6}; align {:.6}; bank {}; intersections {}",
history_base_text(&value.base),
embedded_name(value.sweep_entity.as_ref()),
embedded_name(value.path_entity.as_ref()),
value.direction.x,
value.direction.y,
value.direction.z,
value.draft_angle,
value.scale_factor,
value.twist_angle,
value.align_angle,
value.bank,
value.check_intersections
),
SolidHistoryOperation::Loft(value) => format!(
"{}; cross sections {}; guides {}",
history_base_text(&value.base),
value.cross_sections.len(),
value.guides.len()
),
SolidHistoryOperation::Revolve(value) => format!(
"{}; profile {}; axis point {:.6},{:.6},{:.6}; direction {:.6},{:.6}; angle {:.6}; start {:.6}; draft {:.6}; twist {:.6}; close-to-axis {}",
history_base_text(&value.base),
embedded_name(value.sweep_entity.as_ref()),
value.axis_point.x,
value.axis_point.y,
value.axis_point.z,
value.direction.x,
value.direction.y,
value.revolve_angle,
value.start_angle,
value.draft_angle,
value.twist_angle,
value.close_to_axis
),
}
}
fn solid_history_sections(
document: &CadDocument,
cache: &ObjectDataCache,
entity: &EntityType,
) -> Vec<PropSection> {
let Some(history_handle) = entity_history_handle(entity) else {
return Vec::new();
};
let Some(ObjectType::DynamicBlock(history_object)) =
document.objects.get(&history_handle)
else {
return vec![PropSection {
title: "Solid History".to_string(),
props: vec![
ro("Handle", handle_text(history_handle)),
ro("Status", "Referenced history object is unresolved"),
],
}];
};
let DynamicBlockData::SolidHistory(history) = &history_object.data else {
return vec![PropSection {
title: "Solid History".to_string(),
props: vec![
ro("Handle", handle_text(history_handle)),
ro("Status", "Referenced object is not a solid history"),
],
}];
};
let mut nodes = cache
.dynamic_objects
.iter()
.filter_map(|handle| match document.objects.get(handle) {
Some(ObjectType::DynamicBlock(value))
if document.owner_chain_reaches(value.owner, history_handle) =>
{
match &value.data {
DynamicBlockData::SolidHistoryNode(node) => Some(node),
_ => None,
}
}
_ => None,
})
.collect::<Vec<_>>();
nodes.sort_by_key(|node| history_step_id(node).unwrap_or(i32::MAX));
let mut sections = vec![PropSection {
title: "Solid History".to_string(),
props: vec![
ro("Handle", handle_text(history_handle)),
ro("Version", format!("{}.{}", history.major, history.minor)),
ro("Root node", history.history_node_id.to_string()),
ro("Show history", history.show_history.to_string()),
ro("Record history", history.record_history.to_string()),
ro("Steps", nodes.len().to_string()),
],
}];
if !nodes.is_empty() {
sections.push(PropSection {
title: "Construction Steps".to_string(),
props: nodes
.into_iter()
.map(|node| {
let step = history_step_id(node)
.map(|value| value.to_string())
.unwrap_or_else(|| "?".to_string());
ro(
format!("Step {step} · {}", history_operation_name(node)),
history_operation_text(node),
)
})
.collect(),
});
}
sections
}
fn block_value_text(value: &BlockEvalValue) -> String {
match value {
BlockEvalValue::None => "None".to_string(),
BlockEvalValue::Real(value) => format!("{value:.6}"),
BlockEvalValue::Point(value) => format!("{:.6}, {:.6}", value[0], value[1]),
BlockEvalValue::Text(value) => value.clone(),
BlockEvalValue::Long(value) => value.to_string(),
BlockEvalValue::Handle(value) => handle_text(*value),
BlockEvalValue::Short(value) => value.to_string(),
}
}
fn dynamic_data_text(data: &DynamicBlockData) -> String {
match data {
DynamicBlockData::Unknown => "Unknown".to_string(),
DynamicBlockData::Representation(value) => {
format!("block {}; flags {}", handle_text(value.block), value.flags)
}
DynamicBlockData::ProxyNode(value) => format!(
"node {}; parent {}; version {}.{}; value {}",
value.node_id,
value.parent_id,
value.major,
value.minor,
block_value_text(&value.value)
),
DynamicBlockData::GripLocationComponent(value) => format!(
"grip type {}; expression {}; value {}",
value.grip_type,
value.expression,
block_value_text(&value.eval.value)
),
DynamicBlockData::AlignmentGrip(value) | DynamicBlockData::LinearGrip(value) => format!(
"{} at {:.6},{:.6},{:.6}; orientation {:.6},{:.6},{:.6}; cycling {}",
value.grip.element.name,
value.grip.location.x,
value.grip.location.y,
value.grip.location.z,
value.orientation.x,
value.orientation.y,
value.orientation.z,
value.grip.insert_cycling
),
DynamicBlockData::FlipGrip(value) => format!(
"{} at {:.6},{:.6},{:.6}; state {}; orientation {:.6},{:.6},{:.6}",
value.grip.element.name,
value.grip.location.x,
value.grip.location.y,
value.grip.location.z,
value.combined_state,
value.orientation.x,
value.orientation.y,
value.orientation.z
),
DynamicBlockData::LookupGrip(value)
| DynamicBlockData::PolarGrip(value)
| DynamicBlockData::RotationGrip(value)
| DynamicBlockData::VisibilityGrip(value)
| DynamicBlockData::XYGrip(value)
| DynamicBlockData::PropertiesTableGrip(value) => format!(
"{} at {:.6},{:.6},{:.6}; flags {}/{}; cycling {} weight {}",
value.element.name,
value.location.x,
value.location.y,
value.location.z,
value.flags_91,
value.flags_92,
value.insert_cycling,
value.insert_cycling_weight
),
DynamicBlockData::AlignmentParameter(value) => format!(
"{}; perpendicular {}; base {:.6},{:.6},{:.6}; end {:.6},{:.6},{:.6}",
value.parameter.parameter.element.name,
value.align_perpendicular,
value.parameter.definition_base_point.x,
value.parameter.definition_base_point.y,
value.parameter.definition_base_point.z,
value.parameter.definition_end_point.x,
value.parameter.definition_end_point.y,
value.parameter.definition_end_point.z
),
DynamicBlockData::BasePointParameter(value) => format!(
"{}; point {:.6},{:.6},{:.6}; base {:.6},{:.6},{:.6}",
value.parameter.parameter.element.name,
value.point.x,
value.point.y,
value.point.z,
value.base_point.x,
value.base_point.y,
value.base_point.z
),
DynamicBlockData::FlipParameter(value) => format!(
"{}; labels {}/{}; tooltip {}",
value.parameter.parameter.element.name,
value.base_state_label,
value.flipped_state_label,
value.tooltip
),
DynamicBlockData::LinearParameter(value) => format!(
"{}; {} = {:.6}; range {:.6}..{:.6}; increment {:.6}; values {}",
value.parameter.parameter.element.name,
value.distance_name,
value.distance,
value.value_set.minimum,
value.value_set.maximum,
value.value_set.increment,
value.value_set.values.len()
),
DynamicBlockData::LookupParameter(value) => format!(
"{}; {} index {}; description {}",
value.parameter.parameter.element.name,
value.lookup_name,
value.index,
value.lookup_description
),
DynamicBlockData::PointParameter(value) => format!(
"{}; {}; label at {:.6},{:.6},{:.6}",
value.parameter.parameter.element.name,
value.position_name,
value.definition_label_point.x,
value.definition_label_point.y,
value.definition_label_point.z
),
DynamicBlockData::PolarParameter(value) => format!(
"{}; angle {}; distance {}; offset {:.6}",
value.parameter.parameter.element.name,
value.angle_name,
value.distance_name,
value.offset
),
DynamicBlockData::RotationParameter(value) => format!(
"{}; {} = {:.6}; range {:.6}..{:.6}; values {}",
value.parameter.parameter.element.name,
value.angle_name,
value.angle,
value.value_set.minimum,
value.value_set.maximum,
value.value_set.values.len()
),
DynamicBlockData::UserParameter(value) => format!(
"{}; expression {}; value {}; variable {}",
value.parameter.parameter.element.name,
value.expression,
block_value_text(&value.value),
handle_text(value.associated_variable)
),
DynamicBlockData::VisibilityParameter(value) => format!(
"{}; states {}; members {}; point {:.6},{:.6},{:.6}",
value.name,
value.states.len(),
value.all_blocks.len(),
value.def_point.x,
value.def_point.y,
value.def_point.z
),
DynamicBlockData::XYParameter(value) => format!(
"{}; {} = {:.6}; {} = {:.6}",
value.parameter.parameter.element.name,
value.x_label,
value.x_value,
value.y_label,
value.y_value
),
DynamicBlockData::AngularConstraintParameter(value) => format!(
"{} = {:.6}; dependency {}",
value.expression_name,
value.angle,
handle_text(value.constraint.dependency)
),
DynamicBlockData::DiametricConstraintParameter(value)
| DynamicBlockData::RadialConstraintParameter(value) => format!(
"{} = {:.6}; dependency {}",
value.expression_name,
value.distance,
handle_text(value.constraint.dependency)
),
DynamicBlockData::AlignedConstraintParameter(value)
| DynamicBlockData::LinearConstraintParameter(value)
| DynamicBlockData::HorizontalConstraintParameter(value)
| DynamicBlockData::VerticalConstraintParameter(value) => format!(
"{} = {:.6}; dependency {}",
value.expression_name,
value.value,
handle_text(value.constraint.dependency)
),
DynamicBlockData::ParameterDependencyBody(value) => format!(
"{}; dependency version {}; dimension version {}; class version {}",
value.name,
value.dependency_body_version,
value.dimension_base_version,
value.class_version
),
DynamicBlockData::MoveAction(value) => format!(
"{}; dependencies {}; actions {}; offsets {:.6},{:.6}; angle {:.6}",
value.action.element.name,
value.action.dependencies.len(),
value.action.action_ids.len(),
value.offsets.offset_x,
value.offsets.offset_y,
value.offsets.angle_offset
),
DynamicBlockData::FlipAction(value) => format!(
"{}; dependencies {}; actions {}; connections {}",
value.action.element.name,
value.action.dependencies.len(),
value.action.action_ids.len(),
value.connections.len()
),
DynamicBlockData::RotateAction(value) | DynamicBlockData::ScaleAction(value) => format!(
"{}; dependencies {}; actions {}; base {:.6},{:.6},{:.6}; dependent {}",
value.action.action.element.name,
value.action.action.dependencies.len(),
value.action.action.action_ids.len(),
value.action.base_point.x,
value.action.base_point.y,
value.action.base_point.z,
value.action.dependent
),
DynamicBlockData::ArrayAction(value) => format!(
"{}; dependencies {}; columns offset {:.6}; rows offset {:.6}",
value.action.element.name,
value.action.dependencies.len(),
value.column_offset,
value.row_offset
),
DynamicBlockData::LookupAction(value) => format!(
"{}; dependencies {}; table {}×{}; expressions {}; rows {}",
value.action.element.name,
value.action.dependencies.len(),
value.row_count,
value.column_count,
value.expressions.len(),
value.rows.len()
),
DynamicBlockData::StretchAction(value) => format!(
"{}; dependencies {}; points {}; handles {}; codes {}",
value.action.element.name,
value.action.dependencies.len(),
value.points.len(),
value.handles.len(),
value.codes.len()
),
DynamicBlockData::PolarStretchAction(value) => format!(
"{}; dependencies {}; points {}; handles {}; codes {}",
value.action.element.name,
value.action.dependencies.len(),
value.points.len(),
value.handles.len(),
value.codes.len()
),
DynamicBlockData::EvaluationGraph(value) => format!(
"first node {}; nodes {}; edges {}",
value.first_node_id,
value.nodes.len(),
value.edges.len()
),
DynamicBlockData::AngularConstraintParameterEntity(value) => format!(
"{} = {:.6}; dependency {}",
value.expression_name,
value.angle,
handle_text(value.constraint.dependency)
),
DynamicBlockData::SolidHistory(value) => format!(
"version {}.{}; root {}; show {}; record {}",
value.major,
value.minor,
value.history_node_id,
value.show_history,
value.record_history
),
DynamicBlockData::SolidHistoryNode(value) => history_operation_text(value),
DynamicBlockData::PropertiesTable
| DynamicBlockData::AlignmentParameterEntity
| DynamicBlockData::BasePointParameterEntity
| DynamicBlockData::FlipParameterEntity
| DynamicBlockData::LinearParameterEntity
| DynamicBlockData::PointParameterEntity
| DynamicBlockData::RotationParameterEntity
| DynamicBlockData::VisibilityParameterEntity
| DynamicBlockData::XYParameterEntity
| DynamicBlockData::FlipGripEntity
| DynamicBlockData::LinearGripEntity
| DynamicBlockData::PolarGripEntity
| DynamicBlockData::RotationGripEntity
| DynamicBlockData::VisibilityGripEntity
| DynamicBlockData::XYGripEntity => "Semantic entity marker".to_string(),
}
}
fn dynamic_block_sections(
document: &CadDocument,
cache: &ObjectDataCache,
handle: Handle,
) -> Vec<PropSection> {
let Some(definition) = document.dynamic_definition_for_insert(handle) else {
return Vec::new();
};
let mut objects = cache
.dynamic_objects
.iter()
.filter_map(|object_handle| match document.objects.get(object_handle) {
Some(ObjectType::DynamicBlock(value))
if document.owner_chain_reaches(value.owner, definition) =>
{
Some(value)
}
_ => None,
})
.collect::<Vec<_>>();
let visibility = document.block_visibility_param_for_def(definition);
if objects.is_empty() && visibility.is_none() {
return Vec::new();
}
objects.sort_by_key(|object| object.handle.value());
let mut overview = vec![
ro("Definition", handle_text(definition)),
ro("Native records", objects.len().to_string()),
];
if let Some(parameter) = visibility {
overview.push(ro("Visibility", parameter.name.clone()));
overview.push(ro(
"Visibility states",
parameter
.states
.iter()
.map(|state| state.name.as_str())
.collect::<Vec<_>>()
.join(", "),
));
}
let mut sections = vec![PropSection {
title: "Dynamic Block".to_string(),
props: overview,
}];
if !objects.is_empty() {
sections.push(PropSection {
title: "Dynamic Parameters / Actions".to_string(),
props: objects
.into_iter()
.filter(|object| {
!matches!(
object.data,
DynamicBlockData::SolidHistory(_)
| DynamicBlockData::SolidHistoryNode(_)
)
})
.map(|object| {
let class = if object.dxf_name.is_empty() {
object.cpp_class_name.as_str()
} else {
object.dxf_name.as_str()
};
ro(
format!("{} · {}", handle_text(object.handle), class),
dynamic_data_text(&object.data),
)
})
.collect(),
});
}
sections
}
fn associative_data_text(data: &AssociativeData) -> String {
match data {
AssociativeData::Unknown => "Unknown".to_string(),
AssociativeData::Dependency(value) => format!(
"status {}; read {}; write {}; attached {}; target {}; body {}",
value.status,
value.is_read_dependency,
value.is_write_dependency,
value.is_attached_to_object,
handle_text(value.dependent_on),
handle_text(value.dependency_body)
),
AssociativeData::ValueDependency(value) => format!(
"{}; target {}; value {:?}",
value.name,
handle_text(value.dependency.dependent_on),
value.value.value
),
AssociativeData::GeomDependency(value) => format!(
"target {}; enabled {}; subentity {}",
handle_text(value.dependency.dependent_on),
value.enabled,
value.persistent_subent.class_name
),
AssociativeData::Action(value) => format!(
"status {}; network {}; body {}; dependencies {}; parameters {}; values {}",
value.geometry_status,
handle_text(value.owning_network),
handle_text(value.action_body),
value.dependencies.len(),
value.owned_parameters.len(),
value.values.len()
),
AssociativeData::Network(value) => format!(
"actions {}; owned {}; dependencies {}; values {}",
value.actions.len(),
value.owned_actions.len(),
value.action.dependencies.len(),
value.action.values.len()
),
AssociativeData::ConstraintGroup(value) => format!(
"status {}; dependency {}; actions {}; nodes {}",
value.action.geometry_status,
handle_text(value.dependency),
value.actions.len(),
value.nodes.len()
),
AssociativeData::Variable(value) => format!(
"{} = {:?}; expression {}; evaluator {}; values {}",
value.name,
value.value.value,
value.expression,
value.evaluator,
value.action.values.len()
),
AssociativeData::ArrayParameters(value) => format!(
"{}; items {}; rows {}; levels {}",
value.class_name,
value.items.len(),
value.row_count,
value.level_count
),
AssociativeData::ArrayActionBody(value) => format!(
"parameter block {}; dependencies {}; value parameters {}",
value.parameter_block,
value.parameter_body.dependencies.len(),
value.parameter_body.values.len()
),
AssociativeData::ArrayModifyActionBody(value) => format!(
"items {}; dependencies {}; value parameters {}",
value.item_locations.len(),
value.body.parameter_body.dependencies.len(),
value.body.parameter_body.values.len()
),
AssociativeData::DimensionAssociation(value) => format!(
"dimension {}; associativity {}; references {}",
handle_text(value.dimension),
value.associativity,
value.references.iter().flatten().count()
),
other => format!("{} semantic payload", associative_data_name(other)),
}
}
fn associative_data_name(data: &AssociativeData) -> &'static str {
match data {
AssociativeData::Unknown => "Unknown",
AssociativeData::Dependency(_) => "Dependency",
AssociativeData::ValueDependency(_) => "Value Dependency",
AssociativeData::GeomDependency(_) => "Geometry Dependency",
AssociativeData::SurfaceActionBody(_) => "Surface Action",
AssociativeData::Action(_) => "Action",
AssociativeData::Network(_) => "Network",
AssociativeData::AnnotationActionBody(_) => "Annotation Action",
AssociativeData::PersSubentManager(_) => "Persistent Subentity Manager",
AssociativeData::EdgeActionParam(_) => "Edge Parameter",
AssociativeData::ConstraintGroup(_) => "Constraint Group",
AssociativeData::Variable(_) => "Variable",
AssociativeData::ActionParam(_) => "Action Parameter",
AssociativeData::CompoundActionParam(_) => "Compound Parameter",
AssociativeData::OsnapPointRefActionParam(_) => "Object Snap Parameter",
AssociativeData::PointRefActionParam(_) => "Point Reference",
AssociativeData::ObjectActionParam(_) => "Object Parameter",
AssociativeData::PathActionParam(_) => "Path Parameter",
AssociativeData::DimDependencyBody(_) => "Dimension Dependency",
AssociativeData::FaceActionParam(_) => "Face Parameter",
AssociativeData::VertexActionParam(_) => "Vertex Parameter",
AssociativeData::AsmBodyActionParam(_) => "ASM Body Parameter",
AssociativeData::ArrayParameters(_) => "Array Parameters",
AssociativeData::ArrayActionBody(_) => "Array Action",
AssociativeData::ArrayModifyActionBody(_) => "Array Modify Action",
AssociativeData::DimensionAssociation(_) => "Dimension Association",
AssociativeData::PersSubentManagerStatic(_) => "Static Subentity Manager",
AssociativeData::ViewRepActionBody(_) => "View Representation Action",
AssociativeData::ViewObjectActionParam(_) => "View Object Parameter",
AssociativeData::ViewRepHatchManager(_) => "View Hatch Manager",
AssociativeData::ViewRepHatchActionParam(_) => "View Hatch Parameter",
AssociativeData::ViewLabelActionParam(_) => "View Label Parameter",
}
}
fn associative_sections(
document: &CadDocument,
cache: &ObjectDataCache,
handle: Handle,
) -> Vec<PropSection> {
let mut objects = cache
.associative_objects
.iter()
.filter_map(|object_handle| match document.objects.get(object_handle) {
Some(ObjectType::Associative(value))
if document.owner_chain_reaches(value.owner, handle)
|| value.references_handle(handle) =>
{
Some(value)
}
_ => None,
})
.collect::<Vec<_>>();
if objects.is_empty() {
return Vec::new();
}
objects.sort_by_key(|object| object.handle.value());
vec![PropSection {
title: "Associative Data".to_string(),
props: objects
.into_iter()
.map(|object| {
let class = if object.dxf_name.is_empty() {
object.cpp_class_name.as_str()
} else {
object.dxf_name.as_str()
};
ro(
format!("{} · {}", handle_text(object.handle), class),
associative_data_text(&object.data),
)
})
.collect(),
}]
}
/// Semantic DWG object sections connected to one selected entity.
pub fn sections(
document: &CadDocument,
cache: &ObjectDataCache,
handle: Handle,
entity: &EntityType,
) -> Vec<PropSection> {
let mut sections = solid_history_sections(document, cache, entity);
if matches!(entity, EntityType::Insert(_)) {
sections.extend(dynamic_block_sections(document, cache, handle));
}
if let EntityType::Table(table) = entity {
let mut linked = rustc_hash::FxHashSet::default();
for row in &table.rows {
for cell in &row.cells {
if let Some(link_handle) = cell.data_link_handle {
if linked.insert(link_handle) {
if let Some(ObjectType::ClassObject(object)) =
document.objects.get(&link_handle)
{
if let Some(section) = class_object_section(&object.data) {
sections.push(section);
}
}
}
}
}
}
}
sections.extend(associative_sections(document, cache, handle));
// A directly attached typed object can still be useful when its semantic
// graph contains no backlink (for example a sparse extension dictionary).
if sections.is_empty() {
if let Some(ObjectType::DynamicBlock(object)) = document.objects.get(&handle) {
sections.push(PropSection {
title: "Dynamic Object".to_string(),
props: vec![ro("Decoded data", dynamic_data_text(&object.data))],
});
}
}
sections
}
fn preview_text(value: &str, limit: usize) -> String {
if value.chars().count() <= limit {
return value.to_string();
}
let mut preview: String = value.chars().take(limit).collect();
preview.push_str(&format!("… ({} chars)", value.chars().count()));
preview
}
fn bounded_join<I>(values: I, limit: usize) -> String
where
I: IntoIterator<Item = String>,
{
let mut values = values.into_iter();
let mut visible = Vec::new();
for _ in 0..limit {
let Some(value) = values.next() else {
break;
};
visible.push(value);
}
let hidden = values.count();
let mut result = visible.join("; ");
if hidden != 0 {
result.push_str(&format!("; … +{hidden}"));
}
result
}
fn class_object_section(
data: &acadrust::objects::ClassObjectData,
) -> Option<PropSection> {
use acadrust::objects::ClassObjectData;
let section = match data {
ClassObjectData::Empty => return None,
ClassObjectData::SpatialIndex(value) => PropSection {
title: "Spatial Index".to_string(),
props: vec![
ro(
"Updated",
format!(
"Julian {}; {} ms",
value.last_updated_julian_day,
value.last_updated_milliseconds
),
),
ro(
"Extents",
format!(
"{:.6},{:.6},{:.6} → {:.6},{:.6},{:.6}",
value.min_corner.x,
value.min_corner.y,
value.min_corner.z,
value.max_corner.x,
value.max_corner.y,
value.max_corner.z
),
),
ro("Indexed Objects", value.indexed_objects.len().to_string()),
],
},
ClassObjectData::LayerFilter(value) => PropSection {
title: "Layer Filter".to_string(),
props: vec![ro("Names", bounded_join(value.names.iter().cloned(), 64))],
},
ClassObjectData::PartialViewingIndex(value) => PropSection {
title: "Partial Viewing Index".to_string(),
props: vec![
ro("Has Entries", value.has_entries.to_string()),
ro(
"Entries",
bounded_join(
value.entries.iter().map(|entry| {
format!(
"{}: {:.4},{:.4},{:.4} → {:.4},{:.4},{:.4}",
handle_text(entry.object),
entry.extents_min.x,
entry.extents_min.y,
entry.extents_min.z,
entry.extents_max.x,
entry.extents_max.y,
entry.extents_max.z
)
}),
32,
),
),
],
},
ClassObjectData::VbaProject(value) => PropSection {
title: "VBA Project".to_string(),
props: vec![
ro(
"Detached Records",
format!(
"{} leading / {} bytes; {} trailing / {} bytes",
value.storage.leading_records.len(),
value
.storage
.leading_records
.iter()
.map(|record| record.data.len())
.sum::<usize>(),
value.storage.trailing_records.len(),
value
.storage
.trailing_records
.iter()
.map(|record| record.data.len())
.sum::<usize>()
),
),
ro(
"Streams",
value
.storage
.compound_file
.as_ref()
.map(|storage| storage.root.entries.len())
.unwrap_or_default()
.to_string(),
),
],
},
ClassObjectData::SectionManager(value) => PropSection {
title: "Section Manager".to_string(),
props: vec![
ro("Live", value.is_live.to_string()),
ro(
"Sections",
bounded_join(
value.sections.iter().map(|handle| handle_text(*handle)),
64,
),
),
],
},
ClassObjectData::SectionSettings(value) => PropSection {
title: "Section Settings".to_string(),
props: vec![
ro("Current Type", value.current_type.to_string()),
ro(
"Types",
bounded_join(
value.types.iter().map(|setting| {
let geometry = bounded_join(
setting.geometry.iter().map(|geometry| {
format!(
"#{} flags {} layer '{}' linetype '{}' scale {:.4} weight {} transparency {}/{} hatch {} '{}' angle {:.4} spacing {:.4} scale {:.4} color {:?}",
geometry.index,
geometry.flags,
geometry.layer,
geometry.linetype,
geometry.linetype_scale,
geometry.lineweight,
geometry.face_transparency,
geometry.edge_transparency,
geometry.hatch_type,
geometry.hatch_pattern,
geometry.hatch_angle,
geometry.hatch_spacing,
geometry.hatch_scale,
geometry.color
)
}),
16,
);
format!(
"type {} generation {} sources [{}] destination {} file '{}' geometry {} [{}]",
setting.section_type,
setting.generation,
bounded_join(
setting.sources.iter().map(|handle| handle_text(*handle)),
32
),
handle_text(setting.destination_block),
setting.destination_file,
setting.geometry.len(),
geometry
)
}),
16,
),
),
],
},
ClassObjectData::GradientBackground(value) => PropSection {
title: "Gradient Background".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro(
"Colors",
format!(
"#{:06X} / #{:06X} / #{:06X}",
value.color_top & 0x00FF_FFFF,
value.color_middle & 0x00FF_FFFF,
value.color_bottom & 0x00FF_FFFF
),
),
ro(
"Geometry",
format!(
"horizon {:.6}; height {:.6}; rotation {:.6}",
value.horizon, value.height, value.rotation
),
),
],
},
ClassObjectData::GroundPlaneBackground(value) => PropSection {
title: "Ground Plane Background".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro(
"Colors",
format!(
"sky #{:06X}/#{:06X}; underground #{:06X}/#{:06X}; near/far #{:06X}/#{:06X}",
value.color_sky_zenith & 0x00FF_FFFF,
value.color_sky_horizon & 0x00FF_FFFF,
value.color_underground_horizon & 0x00FF_FFFF,
value.color_underground_azimuth & 0x00FF_FFFF,
value.color_near & 0x00FF_FFFF,
value.color_far & 0x00FF_FFFF
),
),
],
},
ClassObjectData::IblBackground(value) => PropSection {
title: "Image Based Lighting Background".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro("Enabled", value.enabled.to_string()),
ro("Name", value.name.clone()),
ro("Rotation", format!("{:.6}", value.rotation)),
ro("Display Image", value.display_image.to_string()),
ro(
"Secondary Background",
handle_text(value.secondary_background),
),
],
},
ClassObjectData::ImageBackground(value) => PropSection {
title: "Image Background".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro("File", value.filename.clone()),
ro(
"Display",
format!(
"fit {}; aspect {}; tile {}",
value.fit_to_screen,
value.maintain_aspect_ratio,
value.use_tiling
),
),
ro(
"Offset / Scale",
format!(
"{:.6},{:.6} / {:.6},{:.6}",
value.offset.x, value.offset.y, value.scale.x, value.scale.y
),
),
],
},
ClassObjectData::SkyLightBackground(value) => PropSection {
title: "Sky Light Background".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro("Sun", handle_text(value.sun)),
],
},
ClassObjectData::SolidBackground(value) => PropSection {
title: "Solid Background".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro("Color", format!("#{:06X}", value.color & 0x00FF_FFFF)),
],
},
ClassObjectData::RenderEntry(value) => PropSection {
title: "Render History Entry".to_string(),
props: vec![
ro("Image", value.image_filename.clone()),
ro(
"Preset / View",
format!("{} / {}", value.preset_name, value.view_name),
),
ro("Size", format!("{}×{}", value.width, value.height)),
ro(
"Started",
format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}",
value.start_year,
value.start_month,
value.start_day,
value.start_hour,
value.start_minute,
value.start_second,
value.start_millisecond
),
),
ro(
"Finished",
format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}",
value.end_year,
value.end_month,
value.end_day,
value.end_hour,
value.end_minute,
value.end_second,
value.end_millisecond
),
),
ro(
"Statistics",
format!(
"{:.4}s; {} memory; {} materials; {} lights; {} triangles; index {}",
value.render_time,
value.memory_amount,
value.material_count,
value.light_count,
value.triangle_count,
value.display_index
),
),
],
},
ClassObjectData::MotionPath(value) => PropSection {
title: "Motion Path".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro(
"Camera / Target / View",
format!(
"{} / {} / {}",
handle_text(value.camera_path),
handle_text(value.target_path),
handle_text(value.view)
),
),
ro(
"Playback",
format!(
"{} frames at {} fps; corner deceleration {}",
value.frames, value.frame_rate, value.corner_deceleration
),
),
],
},
ClassObjectData::CurvePath(value) => PropSection {
title: "Curve Path".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro("Entity", handle_text(value.entity)),
],
},
ClassObjectData::PointPath(value) => PropSection {
title: "Point Path".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro(
"Point",
format!("{:.8}, {:.8}, {:.8}", value.point.x, value.point.y, value.point.z),
),
],
},
ClassObjectData::TvDeviceProperties(value) => PropSection {
title: "Graphics Device Properties".to_string(),
props: vec![
ro("Flags", value.flags.to_string()),
ro("Max Regen Threads", value.max_regen_threads.to_string()),
ro(
"Palette / Highlight",
format!(
"{} / {} / {}",
value.use_lut_palette,
value.alternate_highlight,
value.alternate_highlight_color
),
),
ro(
"GPU",
format!(
"geometry shader {}; blending {}; AA {:.4}; reserved {:.4}",
value.geometry_shader_usage,
value.blending_mode,
value.antialiasing_level,
value.reserved_double
),
),
],
},
ClassObjectData::PointCloudDefinition(value)
| ClassObjectData::PointCloudDefinitionEx(value) => PropSection {
title: "Point Cloud Definition".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro("File", value.source_filename.clone()),
ro("Loaded", value.is_loaded.to_string()),
ro("Points", value.point_count.to_string()),
ro(
"Extents",
format!(
"{:.6},{:.6},{:.6} → {:.6},{:.6},{:.6}",
value.extents_min.x,
value.extents_min.y,
value.extents_min.z,
value.extents_max.x,
value.extents_max.y,
value.extents_max.z
),
),
],
},
ClassObjectData::PointCloudDefinitionReactor(value)
| ClassObjectData::PointCloudDefinitionReactorEx(value) => PropSection {
title: "Point Cloud Reactor".to_string(),
props: vec![ro("Version", value.class_version.to_string())],
},
ClassObjectData::PointCloudColorMap(value) => PropSection {
title: "Point Cloud Color Map".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro(
"Defaults",
format!(
"intensity '{}'; elevation '{}'; classification '{}'",
value.default_intensity_scheme,
value.default_elevation_scheme,
value.default_classification_scheme
),
),
ro(
"Ramps",
bounded_join(
value.color_ramps.iter().map(|ramp| {
format!(
"v{} [{}]",
ramp.class_version,
ramp.color_schemes.join(", ")
)
}),
32,
),
),
ro(
"Classification Ramps",
bounded_join(
value.classification_color_ramps.iter().map(|ramp| {
format!(
"v{} [{}]",
ramp.class_version,
ramp.color_schemes.join(", ")
)
}),
32,
),
),
],
},
ClassObjectData::NavisworksModelDefinition(value) => PropSection {
title: "Coordination Model Definition".to_string(),
props: vec![
ro("Path", value.path.clone()),
ro(
"State",
format!(
"flags {}; status {}; host visible {}",
value.flags, value.status, value.host_drawing_visibility
),
),
ro(
"Extents",
format!(
"{:.6},{:.6},{:.6} → {:.6},{:.6},{:.6}",
value.extents_min.x,
value.extents_min.y,
value.extents_min.z,
value.extents_max.x,
value.extents_max.y,
value.extents_max.z
),
),
],
},
ClassObjectData::ContextDataManager(value) => PropSection {
title: "Context Data Manager".to_string(),
props: vec![
ro("Object Context", handle_text(value.object_context)),
ro(
"Sub-Managers",
bounded_join(
value.sub_managers.iter().map(|manager| {
format!(
"{} [{}]",
handle_text(manager.handle),
bounded_join(
manager.entries.iter().map(|entry| format!(
"{}={}",
entry.name,
handle_text(entry.item)
)),
32
)
)
}),
32,
),
),
],
},
ClassObjectData::SunStudy(value) => PropSection {
title: "Sun Study".to_string(),
props: vec![
ro("Name", value.setup_name.clone()),
ro("Description", value.description.clone()),
ro(
"Output",
format!(
"type {}; subset {} '{} / {}'; calendar {}; range {}",
value.output_type,
value.use_subset,
value.sheet_set_name,
value.sheet_subset_name,
value.select_dates_from_calendar,
value.select_range_of_dates
),
),
ro(
"Dates",
bounded_join(
value.dates.iter().map(|date| {
format!("Julian {} @ {} ms", date.julian_day, date.milliseconds)
}),
64,
),
),
ro(
"Time",
format!(
"{}..{} every {}; active hours {}",
value.start_time,
value.end_time,
value.interval,
value.hours.iter().filter(|enabled| **enabled).count()
),
),
ro(
"Layout",
format!(
"shade {}; viewports {}; {}×{} spacing {:.6}; lock {}; labels {}",
value.shade_plot_type,
value.viewport_count,
value.rows,
value.columns,
value.spacing,
value.lock_viewports,
value.label_viewports
),
),
ro(
"References",
format!(
"wizard {}; view {}; visual style {}; text style {}",
handle_text(value.page_setup_wizard),
handle_text(value.view),
handle_text(value.visual_style),
handle_text(value.text_style)
),
),
],
},
ClassObjectData::DataTable(value) => PropSection {
title: format!("Data Table · {}", value.name),
props: vec![
ro(
"Shape",
format!(
"{} rows; {} columns; flags {}",
value.row_count,
value.columns.len(),
value.flags
),
),
ro(
"Columns",
bounded_join(
value.columns.iter().map(|column| {
format!(
"{} type {} rows [{}]",
column.name,
column.value_type,
bounded_join(
column.rows.iter().map(|cell| format!(
"{} | {:.8} | {}",
cell.integer,
cell.real,
preview_text(&cell.text, 128)
)),
32
)
)
}),
32,
),
),
],
},
ClassObjectData::DataLink(value) => PropSection {
title: "Data Link".to_string(),
props: vec![
ro("Adapter", value.data_adapter.clone()),
ro("Description", value.description.clone()),
ro("Tooltip", value.tooltip.clone()),
ro(
"Connection",
preview_text(&value.connection_string, 1024),
),
ro(
"Options",
format!(
"{} / update {}; flags {}; path {}; status {} '{}'",
value.option,
value.update_option,
value.flags,
value.path_option,
value.status_flags,
value.update_status
),
),
ro(
"Updated",
format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}",
value.year,
value.month,
value.day,
value.hour,
value.minute,
value.second,
value.millisecond
),
),
ro("Owner", handle_text(value.hard_owner)),
ro(
"Custom Data",
bounded_join(
value.custom_data.iter().map(|entry| {
format!(
"{}='{}'",
handle_text(entry.target),
preview_text(&entry.value, 256)
)
}),
64,
),
),
],
},
ClassObjectData::PersistentSubentityManager(value) => PropSection {
title: "Persistent Subentity Manager".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro(
"Reserved",
format!("{}/{}", value.reserved_zero, value.reserved_two),
),
ro(
"Steps",
format!(
"{} declared; [{}]",
value.associated_step_count,
bounded_join(value.steps.iter().map(ToString::to_string), 128)
),
),
ro(
"Subentities",
format!(
"{} declared; [{}]",
value.associated_subentity_count,
bounded_join(value.subentities.iter().map(ToString::to_string), 128)
),
),
],
},
ClassObjectData::GeoMapImage(value) => PropSection {
title: "Geographic Map Image".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro(
"Origin / Size",
format!(
"{:.8},{:.8},{:.8} / {:.4}×{:.4}",
value.origin.x,
value.origin.y,
value.origin.z,
value.image_size.x,
value.image_size.y
),
),
ro(
"Display",
format!(
"properties {}; clip {}; brightness {}; contrast {}; fade {}",
value.display_properties,
value.clipping_enabled,
value.brightness,
value.contrast,
value.fade
),
),
],
},
ClassObjectData::DetailViewStyle(value) => PropSection {
title: format!("Detail View Style · {}", value.base.display_name),
props: vec![
ro("Description", value.base.description.clone()),
ro(
"State",
format!(
"base v{} flags {}; modified {}; v{} flags {}",
value.base.class_version,
value.base.flags,
value.base.modified_for_recompute,
value.class_version,
value.flags
),
),
ro(
"Identifier",
format!(
"style {}; color {:?}; height {:.6}; excluded '{}'; offset {:.6}; placement {}",
handle_text(value.identifier_style),
value.identifier_color,
value.identifier_height,
value.identifier_excluded_characters,
value.identifier_offset,
value.identifier_placement
),
),
ro(
"Arrow / Boundary",
format!(
"symbol {} color {:?} size {:.6}; boundary {} weight {} color {:?}",
handle_text(value.arrow_symbol),
value.arrow_symbol_color,
value.arrow_symbol_size,
handle_text(value.boundary_linetype),
value.boundary_lineweight,
value.boundary_color
),
),
ro(
"View Label",
format!(
"style {} color {:?} height {:.6}; attachment {}; offset {:.6}; alignment {}; pattern '{}'",
handle_text(value.view_label_text_style),
value.view_label_text_color,
value.view_label_text_height,
value.view_label_attachment,
value.view_label_offset,
value.view_label_alignment,
value.view_label_pattern
),
),
ro(
"Connection / Border",
format!(
"{} weight {} color {:?}; {} weight {} color {:?}; model edge {}",
handle_text(value.connection_linetype),
value.connection_lineweight,
value.connection_color,
handle_text(value.border_linetype),
value.border_lineweight,
value.border_color,
value.model_edge
),
),
],
},
ClassObjectData::SectionViewStyle(value) => PropSection {
title: format!("Section View Style · {}", value.base.display_name),
props: vec![
ro("Description", value.base.description.clone()),
ro(
"State",
format!(
"base v{} flags {}; modified {}; v{} flags {}; reserved {:?}",
value.base.class_version,
value.base.flags,
value.base.modified_for_recompute,
value.class_version,
value.flags,
value.reserved_flags
),
),
ro(
"Identifier / Arrows",
format!(
"style {} color {:?} height {:.6}; symbols {}/{} color {:?} size {:.6}; excluded '{}'; extension {:.6}",
handle_text(value.identifier_style),
value.identifier_color,
value.identifier_height,
handle_text(value.arrow_start_symbol),
handle_text(value.arrow_end_symbol),
value.arrow_symbol_color,
value.arrow_symbol_size,
value.identifier_excluded_characters,
value.arrow_symbol_extension_length
),
),
ro(
"Plane / Bend",
format!(
"plane {} weight {} color {:?}; bend {} weight {} color {:?}; lengths {:.6}/{:.6}",
handle_text(value.plane_linetype),
value.plane_lineweight,
value.plane_color,
handle_text(value.bend_linetype),
value.bend_lineweight,
value.bend_color,
value.bend_line_length,
value.end_line_length
),
),
ro(
"View Label",
format!(
"style {} color {:?} height {:.6}; attachment {}; offset {:.6}; alignment {}; pattern '{}'",
handle_text(value.view_label_text_style),
value.view_label_text_color,
value.view_label_text_height,
value.view_label_attachment,
value.view_label_offset,
value.view_label_alignment,
value.view_label_pattern
),
),
ro(
"Hatch",
format!(
"color {:?}; background {:?}; pattern '{}'; scale {:.6}; transparency {}; angles [{}]",
value.hatch_color,
value.hatch_background_color,
value.hatch_pattern,
value.hatch_scale,
value.hatch_transparency,
bounded_join(value.hatch_angles.iter().map(|angle| format!("{angle:.6}")), 64)
),
),
ro(
"Placement",
format!(
"identifier {} offset {:.6}; arrow {}; overshoot {:.6}",
value.identifier_position,
value.identifier_offset,
value.arrow_position,
value.end_line_overshoot
),
),
],
},
ClassObjectData::AcMeCommandHistory(value) => PropSection {
title: "Model Documentation Command History".to_string(),
props: vec![ro("Version", value.class_version.to_string())],
},
ClassObjectData::AcMeScope(value) => PropSection {
title: "Model Documentation Scope".to_string(),
props: vec![ro("Version", value.class_version.to_string())],
},
ClassObjectData::AcMeStateManager(value) => PropSection {
title: "Model Documentation State Manager".to_string(),
props: vec![ro("Version", value.class_version.to_string())],
},
ClassObjectData::CsacDocumentOptions(value) => PropSection {
title: "Model Documentation Options".to_string(),
props: vec![ro("Version", value.class_version.to_string())],
},
ClassObjectData::ViewRepSourceManager(value) => PropSection {
title: "View Representation Source Manager".to_string(),
props: vec![
ro("Has Source", value.has_source.to_string()),
ro("Source", handle_text(value.source)),
ro("Status", value.status.to_string()),
],
},
ClassObjectData::ViewRepStandard(value) => PropSection {
title: "View Representation Standard".to_string(),
props: vec![ro("Values", format!("{:?}", value.values))],
},
ClassObjectData::ViewRepOrientationDefinition => PropSection {
title: "View Representation Orientation Definition".to_string(),
props: Vec::new(),
},
ClassObjectData::ViewRepOrientation(value) => PropSection {
title: "View Representation Orientation".to_string(),
props: vec![
ro(
"Camera",
format!("{:.8},{:.8},{:.8}", value.camera.x, value.camera.y, value.camera.z),
),
ro(
"Target",
format!("{:.8},{:.8},{:.8}", value.target.x, value.target.y, value.target.z),
),
ro(
"Normal",
format!("{:.8},{:.8},{:.8}", value.normal.x, value.normal.y, value.normal.z),
),
],
},
ClassObjectData::ViewRepSectionDefinition(value) => PropSection {
title: "View Representation Section Definition".to_string(),
props: vec![
ro("Version", value.version.to_string()),
ro("Depth", format!("{:.8}", value.section_depth)),
ro("Flags", format!("{:?}", value.flags)),
],
},
ClassObjectData::ViewRepModelSpaceViewSelectionSet(value) => PropSection {
title: "View Representation Selection Set".to_string(),
props: vec![
ro("Version", value.version.to_string()),
ro(
"Entities",
bounded_join(
value.entities.iter().map(|handle| handle_text(*handle)),
128,
),
),
],
},
ClassObjectData::ViewRep(value) => PropSection {
title: format!("View Representation · {}", value.name),
props: vec![
ro(
"Header",
format!(
"{:?}; scale {}; status {}; description '{}'",
value.header_values, value.scale, value.header_status, value.description
),
),
ro(
"Source",
format!(
"id {}; enabled {}; version {}; model {}; guid {:08X}-{:04X}-{:04X}-{:?}",
value.source_id,
value.source_enabled,
value.source_version,
value.model_id,
value.guid.data1,
value.guid.data2,
value.guid.data3,
value.guid.data4
),
),
ro(
"Geometry",
format!(
"marker {}; transform v{} {:?}; database {}; geometry v{} marker {}; sketches {}",
value.marker,
value.transform_version,
value.transform,
value.database_id,
value.geometry_version,
value.geometry_marker,
value.sketches.len()
),
),
ro(
"References",
format!(
"related {:?}; source manager {}; owned {:?}; optional {:?}; orientation {}; linked {:?}; style {}",
value.related_objects.map(handle_text),
handle_text(value.source_manager),
value.owned_objects.map(handle_text),
value.optional_objects.map(handle_text),
handle_text(value.orientation),
value.linked_views.map(handle_text),
handle_text(value.style)
),
),
ro(
"Placement",
format!(
"{:.8},{:.8}; rotation {:.8}; active {}; projection {}",
value.position.x,
value.position.y,
value.rotation,
value.is_active,
value.projection
),
),
ro(
"Actions / Parent",
format!(
"mode {}; action {}; has parent {}; parent {}; section paths {}",
value.action_mode,
value.action.map(handle_text).unwrap_or_else(|| "None".to_string()),
value.has_parent,
handle_text(value.parent),
value.section_sketches.len()
),
),
ro(
"Tail / Block Path",
format!(
"version {}; state {}; id {}; path count {}; version {}; id {}; enabled {}; entries {}",
value.tail_version,
value.tail_state,
value.tail_id,
value.path_count,
value.path_version,
value.path_id,
value.has_block_path,
value.block_path.as_ref().map(|path| path.entries.len()).unwrap_or_default()
),
),
],
},
ClassObjectData::ViewRepModelSpaceSource(value) => PropSection {
title: "View Representation Model Source".to_string(),
props: vec![
ro(
"Header",
format!(
"enabled {}; values {:?}; source v{} status {}; tail {:?}",
value.enabled,
value.header_values,
value.source_version,
value.source_status,
value.tail_values
),
),
ro("Transform", format!("{:?}", value.transform)),
ro(
"References",
format!(
"model {}; references {:?}; orientation {}",
handle_text(value.model),
value.references.map(handle_text),
handle_text(value.orientation)
),
),
ro(
"GUID",
format!(
"{:08X}-{:04X}-{:04X}-{:?}",
value.guid.data1,
value.guid.data2,
value.guid.data3,
value.guid.data4
),
),
],
},
ClassObjectData::LightList(_)
| ClassObjectData::Sun(_)
| ClassObjectData::RenderSettings(_)
| ClassObjectData::MentalRayRenderSettings(_)
| ClassObjectData::RapidRtRenderSettings(_)
| ClassObjectData::RenderEnvironment(_)
| ClassObjectData::RenderGlobal(_) => return None,
};
Some(section)
}
fn semantic_property_text(
value: &acadrust::objects::SemanticPropertyValue,
) -> String {
use acadrust::objects::SemanticPropertyValue;
match value {
SemanticPropertyValue::Text(value) => preview_text(value, 512),
SemanticPropertyValue::Bool(value) => value.to_string(),
SemanticPropertyValue::Byte(value) => value.to_string(),
SemanticPropertyValue::Int16(value) => value.to_string(),
SemanticPropertyValue::Int32(value) => value.to_string(),
SemanticPropertyValue::Int64(value) => value.to_string(),
SemanticPropertyValue::Double(value) => format!("{value:.12}"),
SemanticPropertyValue::Handle(value) => handle_text(*value),
SemanticPropertyValue::Binary(value) => format!("{} bytes", value.len()),
}
}
fn auxiliary_object_property(
object: &ObjectType,
) -> Option<Property> {
use acadrust::objects::DataObjectData;
let (label, value) = match object {
ObjectType::TableContent(table) => (
format!("Table Content {}", handle_text(table.common.handle)),
format!(
"{} rows × {} columns; {} merged ranges; {} fields",
table.rows.len(),
table.columns.len(),
table.merged_ranges.len(),
table.field_handles.len()
),
),
ObjectType::ImageDefinitionReactor(value) => (
format!("Image Reactor {}", handle_text(value.handle)),
format!(
"owner {}; image {}",
handle_text(value.owner),
handle_text(value.image_handle)
),
),
ObjectType::BookColor(value) => (
format!("Book Color {}", handle_text(value.handle)),
format!("{} / {} = {:?}", value.book_name, value.color_name, value.color),
),
ObjectType::PlaceHolder(value) => (
format!("Placeholder {}", handle_text(value.handle)),
format!("owner {}", handle_text(value.owner)),
),
ObjectType::FieldList(value) => (
format!("Field List {}", handle_text(value.handle)),
format!(
"owner {}; unknown {}; fields [{}]",
handle_text(value.owner),
value.unknown,
bounded_join(value.fields.iter().map(|handle| handle_text(*handle)), 128)
),
),
ObjectType::DataObject(value) => {
let text = match &value.data {
DataObjectData::BreakPointRef => "break point reference".to_string(),
DataObjectData::BreakData(data) => format!(
"v{} dimension {}; reserved {}; point references {}",
data.version,
handle_text(data.dimension_reference),
handle_text(data.reserved_reference),
data.point_references.len()
),
DataObjectData::CellStyleMap(data) => {
format!("{} named cell styles", data.cells.len())
}
DataObjectData::AcDsRecord => "AcDs record".to_string(),
DataObjectData::AcDsSchema => "AcDs schema".to_string(),
DataObjectData::Dummy => "dummy helper".to_string(),
DataObjectData::IdBuffer(data) => format!(
"flags {}; objects [{}]",
data.flags,
bounded_join(
data.object_ids.iter().map(|handle| handle_text(*handle)),
128
)
),
DataObjectData::Index(data) => format!(
"updated Julian {} @ {} ms",
data.last_updated_julian_day, data.last_updated_milliseconds
),
DataObjectData::LayerIndex(data) => format!(
"updated Julian {} @ {} ms; entries [{}]",
data.last_updated_julian_day,
data.last_updated_milliseconds,
bounded_join(
data.entries.iter().map(|entry| format!(
"{}: {} layers, buffer {}",
entry.name,
entry.layer_count,
handle_text(entry.id_buffer)
)),
64
)
),
DataObjectData::LongTransaction => "long transaction".to_string(),
DataObjectData::ObjectPointer => "object pointer".to_string(),
DataObjectData::PartialViewingFilter(_) => {
"partial viewing filter".to_string()
}
DataObjectData::TableGeometry(data) => format!(
"{} rows × {} columns; {} cell geometry records; {} primitives",
data.rows,
data.columns,
data.cells.len(),
data.cells.iter().map(|cell| cell.geometry.len()).sum::<usize>()
),
};
(
format!("{} {}", value.dxf_name(), handle_text(value.handle)),
format!("owner {}; {text}", handle_text(value.owner)),
)
}
ObjectType::RegisteredClass(value) => (
format!(
"{} {}",
if value.dxf_name.is_empty() {
value.cpp_class_name.as_str()
} else {
value.dxf_name.as_str()
},
handle_text(value.handle)
),
format!(
"owner {}; properties [{}]; payload {} bits / {} records; references {}",
handle_text(value.owner),
bounded_join(
value.properties.iter().map(|property| format!(
"{}:{}={}",
property.subclass,
property.code,
semantic_property_text(&property.value)
)),
64
),
value.payload.bit_count,
value.payload.records.len(),
value.object_ids.len()
),
),
ObjectType::ProxyObject(value) => (
format!(
"Proxy Object {}",
handle_text(value.handle)
),
format!(
"class {} / id {}; subclass '{}'; version {}/{} maintenance {}; DXF {}; payload {} + {} bits; references {}",
value.class_id,
value.proxy_id,
value.dxf_subclass,
value.version,
value.dwg_version,
value.maintenance_version,
value.from_dxf,
value.payload.bit_count,
value.text_payload.bit_count,
value.object_ids.len()
),
),
ObjectType::Unknown {
type_name,
handle,
owner,
raw_dxf_codes,
raw_dwg_data,
raw_dwg_handle_bits,
..
} => (
format!("Unknown Object {}", handle_text(*handle)),
format!(
"{}; owner {}; DXF codes {}; DWG bytes {}; handle bits {}",
type_name,
handle_text(*owner),
raw_dxf_codes.as_ref().map(Vec::len).unwrap_or_default(),
raw_dwg_data.as_ref().map(Vec::len).unwrap_or_default(),
raw_dwg_handle_bits
),
),
_ => return None,
};
Some(ro(label, value))
}
/// Drawing-level standard object data prepared on the file-open worker.
fn build_document_sections(document: &CadDocument) -> Vec<PropSection> {
use acadrust::objects::ClassObjectData;
let mut sections = vec![PropSection {
title: "Drawing".to_string(),
props: vec![
ro("Version", format!("{:?}", document.version)),
ro("Entities", document.entity_count().to_string()),
ro("Objects", document.objects.len().to_string()),
ro("Layers", document.layers.len().to_string()),
ro("Blocks", document.block_records.len().to_string()),
],
}];
let mut objects = document.objects.iter().collect::<Vec<_>>();
objects.sort_by_key(|(handle, _)| handle.value());
for (_, object) in objects {
match object {
ObjectType::GeoData(value) => {
sections.push(PropSection {
title: "Geographic Location".to_string(),
props: vec![
ro("Version", value.version.to_string()),
ro("Coordinate Type", value.coordinate_type.to_string()),
ro(
"Design Point",
format!(
"{:.8}, {:.8}, {:.8}",
value.design_point.x,
value.design_point.y,
value.design_point.z
),
),
ro(
"Reference Point",
format!(
"{:.8}, {:.8}, {:.8}",
value.reference_point.x,
value.reference_point.y,
value.reference_point.z
),
),
ro(
"North",
format!(
"{:.8}, {:.8}",
value.north_direction.x, value.north_direction.y
),
),
ro(
"Up",
format!(
"{:.8}, {:.8}, {:.8}",
value.up_direction.x,
value.up_direction.y,
value.up_direction.z
),
),
ro(
"Units / Scale",
format!(
"horizontal {} × {:.12}; vertical {} × {:.12}; user {:.12}",
value.horizontal_units,
value.horizontal_unit_scale,
value.vertical_units,
value.vertical_unit_scale,
value.user_scale_factor
),
),
ro(
"Sea Level",
format!(
"enabled {}; elevation {:.8}; radius {:.8}",
value.sea_level_correction,
value.sea_level_elevation,
value.coordinate_projection_radius
),
),
ro(
"Coordinate System",
preview_text(&value.coordinate_system_definition, 1024),
),
ro("Datum", preview_text(&value.coordinate_system_datum, 512)),
ro("WKT", preview_text(&value.coordinate_system_wkt, 1024)),
ro(
"Transformation Mesh",
format!(
"{} points; {} faces",
value.mesh_points.len(),
value.mesh_faces.len()
),
),
ro(
"Observation",
format!(
"{} → {}; coverage {}; RSS {}",
value.observation_from_tag,
value.observation_to_tag,
value.observation_coverage_tag,
value.geo_rss_tag
),
),
],
});
}
ObjectType::RasterVariables(value) => {
sections.push(PropSection {
title: "Raster Settings".to_string(),
props: vec![
ro("Version", value.class_version.to_string()),
ro("Display Frame", value.display_image_frame.to_string()),
ro("Quality", value.image_quality.to_string()),
ro("Units", value.units.to_string()),
],
});
}
ObjectType::WipeoutVariables(value) => {
sections.push(PropSection {
title: "Wipeout Settings".to_string(),
props: vec![ro("Display Frame", value.display_frame.to_string())],
});
}
ObjectType::ClassObject(value) => match &value.data {
ClassObjectData::LightList(list) => {
sections.push(PropSection {
title: "Light List".to_string(),
props: vec![
ro("Version", list.class_version.to_string()),
ro("Lights", list.lights.len().to_string()),
ro(
"Entries",
bounded_join(
list.lights.iter().map(|light| {
format!(
"{}={}",
light.name,
handle_text(light.handle)
)
}),
64,
),
),
],
});
}
ClassObjectData::Sun(sun) => {
sections.push(PropSection {
title: "Sun".to_string(),
props: vec![
ro("Version", sun.class_version.to_string()),
ro("On", sun.is_on.to_string()),
ro("Color", format!("{:?}", sun.color)),
ro("Intensity", format!("{:.6}", sun.intensity)),
ro(
"Date / Time",
format!(
"Julian {}; {} ms; DST {}",
sun.julian_day,
sun.milliseconds,
sun.is_daylight_savings_on
),
),
ro(
"Shadows",
format!(
"enabled {}; type {}; map {}; softness {}",
sun.has_shadow,
sun.shadow_type,
sun.shadow_map_size,
sun.shadow_softness
),
),
],
});
}
ClassObjectData::RenderSettings(settings) => {
sections.push(PropSection {
title: "Render Settings".to_string(),
props: vec![
ro("Version", settings.class_version.to_string()),
ro("Name", settings.name.clone()),
ro("Description", settings.description.clone()),
ro(
"Flags",
format!(
"fog {}; fog background {}; backfaces {}; environment {}",
settings.fog_enabled,
settings.fog_background_enabled,
settings.backfaces_enabled,
settings.environment_image_enabled
),
),
ro(
"Environment Image",
settings.environment_image_filename.clone(),
),
ro(
"Display",
format!(
"index {}; predefined {}",
settings.display_index, settings.has_predefined
),
),
],
});
}
ClassObjectData::MentalRayRenderSettings(settings) => {
sections.push(PropSection {
title: "Mental Ray".to_string(),
props: vec![
ro("Preset", settings.base.name.clone()),
ro(
"Version",
format!(
"base {}; mental ray {}",
settings.base.class_version, settings.version
),
),
ro(
"Preset State",
format!(
"description '{}'; display {}; predefined {}; fog {}/{}; backfaces {}; environment {} '{}'",
settings.base.description,
settings.base.display_index,
settings.base.has_predefined,
settings.base.fog_enabled,
settings.base.fog_background_enabled,
settings.base.backfaces_enabled,
settings.base.environment_image_enabled,
settings.base.environment_image_filename
),
),
ro(
"Sampling",
format!(
"{}..{}; filter {} {:.4}×{:.4}; contrast {:?}",
settings.sampling_min,
settings.sampling_max,
settings.sampling_filter,
settings.sampling_filter_width,
settings.sampling_filter_height,
settings.sampling_contrast
),
),
ro(
"Ray Tracing",
format!(
"{}; depth {:?}; shadows {} / maps {}",
settings.ray_tracing_enabled,
settings.ray_trace_depth,
settings.shadow_mode,
settings.shadow_maps_enabled
),
),
ro(
"Global Illumination",
format!(
"{}; samples {}; radius {} {:.6}; photons {}; trace {:?}",
settings.global_illumination_enabled,
settings.global_illumination_sample_count,
settings.global_illumination_sample_radius_enabled,
settings.global_illumination_sample_radius,
settings.photons_per_light,
settings.photon_trace_depth
),
),
ro(
"Final Gather",
format!(
"{}; rays {}; state {:?}; radius {:?}",
settings.final_gathering_enabled,
settings.final_gathering_ray_count,
settings.final_gathering_sample_radius_state,
settings.final_gathering_sample_radius
),
),
ro(
"Diagnostics",
format!(
"mode {}; grid {} size {:.6}; photon {}; BSP {}; samples {}; luminance {:.6}",
settings.diagnostics_mode,
settings.diagnostics_grid_mode,
settings.diagnostics_grid_size,
settings.diagnostics_photon_mode,
settings.diagnostics_bsp_mode,
settings.diagnostics_samples_mode,
settings.light_luminance_scale
),
),
ro(
"Execution",
format!(
"export {}; description '{}'; tile {} order {}; memory {}; energy {:.6}",
settings.export_mi_enabled,
settings.description,
settings.tile_size,
settings.tile_order,
settings.memory_limit,
settings.energy_multiplier
),
),
],
});
}
ClassObjectData::RapidRtRenderSettings(settings) => {
sections.push(PropSection {
title: "Rapid RT".to_string(),
props: vec![
ro("Preset", settings.base.name.clone()),
ro(
"Version",
format!(
"base {}; Rapid RT {}",
settings.base.class_version, settings.version
),
),
ro(
"Preset State",
format!(
"description '{}'; display {}; predefined {}; fog {}/{}; backfaces {}; environment {} '{}'",
settings.base.description,
settings.base.display_index,
settings.base.has_predefined,
settings.base.fog_enabled,
settings.base.fog_background_enabled,
settings.base.backfaces_enabled,
settings.base.environment_image_enabled,
settings.base.environment_image_filename
),
),
ro(
"Target / Level / Time",
format!(
"{} / {} / {}",
settings.render_target,
settings.render_level,
settings.render_time
),
),
ro(
"Lighting / Filter",
format!(
"{} / {} {:.4}×{:.4}",
settings.lighting_model,
settings.filter_type,
settings.filter_width,
settings.filter_height
),
),
],
});
}
ClassObjectData::RenderEnvironment(environment) => {
sections.push(PropSection {
title: "Render Environment".to_string(),
props: vec![
ro("Version", environment.class_version.to_string()),
ro(
"Fog",
format!(
"{}; background {}; color {:?}; density {:.4}..{:.4}; distance {:.4}..{:.4}",
environment.fog_enabled,
environment.fog_background_enabled,
environment.fog_color,
environment.fog_density_near,
environment.fog_density_far,
environment.fog_distance_near,
environment.fog_distance_far
),
),
ro(
"Environment Image",
format!(
"{} · {}",
environment.environment_image_enabled,
environment.environment_image_filename
),
),
],
});
}
ClassObjectData::RenderGlobal(global) => {
sections.push(PropSection {
title: "Render Output".to_string(),
props: vec![
ro("Version", global.class_version.to_string()),
ro(
"Procedure / Destination",
format!("{} / {}", global.procedure, global.destination),
),
ro(
"Image",
format!(
"{}×{}; save {} · {}",
global.image_width,
global.image_height,
global.save_enabled,
global.save_filename
),
),
ro(
"Display",
format!(
"predefined first {}; high-level info {}",
global.predefined_presets_first,
global.high_level_info
),
),
],
});
}
other => {
if let Some(section) = class_object_section(other) {
sections.push(section);
}
}
},
_ => {}
}
}
if !document.dgn_ls_definitions.is_empty() {
let mut definitions = document
.dgn_ls_definitions
.values()
.collect::<Vec<_>>();
definitions.sort_by_key(|definition| definition.handle.value());
let omitted = definitions.len().saturating_sub(128);
let mut props = definitions
.into_iter()
.take(128)
.map(|definition| {
ro(
definition.name.clone(),
format!(
"handle {}; root {}; components {}",
handle_text(definition.handle),
handle_text(definition.root_component),
document.dgn_ls_components.len()
),
)
})
.collect::<Vec<_>>();
if omitted != 0 {
props.push(ro("More Definitions", format!("{omitted} omitted")));
}
sections.push(PropSection {
title: "DGN Line Styles".to_string(),
props,
});
}
let mut auxiliary = document
.objects
.values()
.filter_map(auxiliary_object_property)
.collect::<Vec<_>>();
auxiliary.sort_by(|left, right| left.label.cmp(&right.label));
let auxiliary_omitted = auxiliary.len().saturating_sub(256);
auxiliary.truncate(256);
if auxiliary_omitted != 0 {
auxiliary.push(ro(
"More Standard Objects",
format!("{auxiliary_omitted} omitted"),
));
}
if !auxiliary.is_empty() {
sections.push(PropSection {
title: "Standard Object Data".to_string(),
props: auxiliary,
});
}
let mut per_title = rustc_hash::FxHashMap::default();
let mut omitted = rustc_hash::FxHashMap::default();
sections.retain(|section| {
if section.title == "Drawing" {
return true;
}
let shown = per_title.entry(section.title.clone()).or_insert(0usize);
if *shown < 4 {
*shown += 1;
true
} else {
*omitted.entry(section.title.clone()).or_insert(0usize) += 1;
false
}
});
if !omitted.is_empty() {
let mut entries = omitted.into_iter().collect::<Vec<_>>();
entries.sort_by(|left, right| left.0.cmp(&right.0));
sections.push(PropSection {
title: "Additional Standard Objects".to_string(),
props: entries
.into_iter()
.map(|(name, count)| ro(name, format!("{count} more records")))
.collect(),
});
}
sections
}