use crate::entities::{Entity, EntityCommon, EntityType, AttributeEntity};
use crate::entities::{Arc, Ellipse};
use crate::types::{
BoundingBox3D, Color, Handle, LineWeight, Matrix3, Matrix4, Transform,
Transparency, Vector3,
};
const SCALE_EPSILON: f64 = 1e-12;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Insert {
pub common: EntityCommon,
pub block_name: String,
pub insert_point: Vector3,
x_scale: f64,
y_scale: f64,
z_scale: f64,
pub rotation: f64,
pub normal: Vector3,
pub column_count: u16,
pub row_count: u16,
pub column_spacing: f64,
pub row_spacing: f64,
pub attributes: Vec<AttributeEntity>,
}
impl Insert {
pub fn new(block_name: impl Into<String>, insert_point: Vector3) -> Self {
Self {
common: EntityCommon::default(),
block_name: block_name.into(),
insert_point,
x_scale: 1.0,
y_scale: 1.0,
z_scale: 1.0,
rotation: 0.0,
normal: Vector3::new(0.0, 0.0, 1.0),
column_count: 1,
row_count: 1,
column_spacing: 0.0,
row_spacing: 0.0,
attributes: Vec::new(),
}
}
pub fn x_scale(&self) -> f64 {
self.x_scale
}
pub fn set_x_scale(&mut self, value: f64) {
self.x_scale = if value.abs() < SCALE_EPSILON {
SCALE_EPSILON
} else {
value
};
}
pub fn y_scale(&self) -> f64 {
self.y_scale
}
pub fn set_y_scale(&mut self, value: f64) {
self.y_scale = if value.abs() < SCALE_EPSILON {
SCALE_EPSILON
} else {
value
};
}
pub fn z_scale(&self) -> f64 {
self.z_scale
}
pub fn set_z_scale(&mut self, value: f64) {
self.z_scale = if value.abs() < SCALE_EPSILON {
SCALE_EPSILON
} else {
value
};
}
pub fn with_scale(mut self, x: f64, y: f64, z: f64) -> Self {
self.set_x_scale(x);
self.set_y_scale(y);
self.set_z_scale(z);
self
}
pub fn with_uniform_scale(mut self, scale: f64) -> Self {
self.set_x_scale(scale);
self.set_y_scale(scale);
self.set_z_scale(scale);
self
}
pub fn with_rotation(mut self, angle: f64) -> Self {
self.rotation = angle;
self
}
pub fn with_normal(mut self, normal: Vector3) -> Self {
self.normal = normal;
self
}
pub fn with_array(mut self, columns: u16, rows: u16, col_spacing: f64, row_spacing: f64) -> Self {
self.column_count = columns;
self.row_count = rows;
self.column_spacing = col_spacing;
self.row_spacing = row_spacing;
self
}
pub fn has_attributes(&self) -> bool {
!self.attributes.is_empty()
}
pub fn is_array(&self) -> bool {
self.column_count > 1 || self.row_count > 1
}
pub fn is_minsert(&self) -> bool {
self.is_array()
}
pub fn instance_count(&self) -> usize {
(self.column_count as usize) * (self.row_count as usize)
}
pub fn array_points(&self) -> Vec<Vector3> {
let mut points = Vec::with_capacity(self.instance_count());
for row in 0..self.row_count {
for col in 0..self.column_count {
let offset_x = col as f64 * self.column_spacing;
let offset_y = row as f64 * self.row_spacing;
let cos_r = self.rotation.cos();
let sin_r = self.rotation.sin();
let rotated_x = offset_x * cos_r - offset_y * sin_r;
let rotated_y = offset_x * sin_r + offset_y * cos_r;
let point = self.insert_point + Vector3::new(rotated_x, rotated_y, 0.0);
points.push(point);
}
}
points
}
pub fn has_uniform_scale(&self) -> bool {
(self.x_scale - self.y_scale).abs() < 1e-10
&& (self.y_scale - self.z_scale).abs() < 1e-10
}
pub fn uniform_scale(&self) -> Option<f64> {
if self.has_uniform_scale() {
Some(self.x_scale)
} else {
None
}
}
pub fn subclass_marker(&self) -> &'static str {
if self.is_minsert() {
"AcDbMInsertBlock"
} else {
"AcDbBlockReference"
}
}
pub fn get_transform(&self) -> Transform {
let ocs = Matrix4::from_matrix3(Matrix3::arbitrary_axis(self.normal));
let translation = Matrix4::translation(
self.insert_point.x,
self.insert_point.y,
self.insert_point.z,
);
let rotation = Matrix4::rotation_z(self.rotation);
let scale = Matrix4::scaling(self.x_scale, self.y_scale, self.z_scale);
Transform::from_matrix(ocs * translation * rotation * scale)
}
fn transform_normal(transform: &Transform, normal: Vector3) -> Vector3 {
let m4 = transform.matrix;
let upper3x3 = Matrix3::from_rows(
[m4.m[0][0], m4.m[0][1], m4.m[0][2]],
[m4.m[1][0], m4.m[1][1], m4.m[1][2]],
[m4.m[2][0], m4.m[2][1], m4.m[2][2]],
);
if let Some(inv) = upper3x3.inverse() {
let inv_t = inv.transpose();
let transformed = inv_t.transform_point(normal);
let len = transformed.length();
if len < 1e-10 {
normal
} else {
transformed * (1.0 / len)
}
} else {
normal }
}
fn resolve_properties(&self, common: &mut EntityCommon) {
if common.layer == "0" {
common.layer = self.common.layer.clone();
}
if common.color == Color::ByBlock {
common.color = self.common.color;
}
if common.line_weight == LineWeight::ByBlock {
common.line_weight = self.common.line_weight;
}
}
pub fn explode(&self, block_entities: &[EntityType]) -> Vec<EntityType> {
let transforms = self.array_transforms();
let mut result = Vec::new();
for transform in &transforms {
for entity in block_entities {
if let Some(exploded) = self.explode_single(entity, transform) {
result.push(exploded);
}
}
}
result
}
fn array_transforms(&self) -> Vec<Transform> {
if !self.is_minsert() {
return vec![self.get_transform()];
}
let ocs = Matrix4::from_matrix3(Matrix3::arbitrary_axis(self.normal));
let rotation = Matrix4::rotation_z(self.rotation);
let scale = Matrix4::scaling(self.x_scale, self.y_scale, self.z_scale);
let mut transforms = Vec::with_capacity(self.instance_count());
for row in 0..self.row_count {
for col in 0..self.column_count {
let offset_x = col as f64 * self.column_spacing;
let offset_y = row as f64 * self.row_spacing;
let cell_pt = Vector3::new(
self.insert_point.x + offset_x,
self.insert_point.y + offset_y,
self.insert_point.z,
);
let translation = Matrix4::translation(cell_pt.x, cell_pt.y, cell_pt.z);
transforms.push(Transform::from_matrix(ocs * translation * rotation * scale));
}
}
transforms
}
fn explode_single(&self, entity: &EntityType, transform: &Transform) -> Option<EntityType> {
match entity {
EntityType::Block(_)
| EntityType::BlockEnd(_)
| EntityType::AttributeDefinition(_) => None,
EntityType::Arc(arc) => {
let sx = self.x_scale.abs();
let sy = self.y_scale.abs();
let is_uniform_xy = (sx - sy).abs() < 1e-10;
if is_uniform_xy {
Some(self.explode_arc_uniform(arc, transform))
} else {
Some(self.explode_arc_to_ellipse(arc, transform))
}
}
EntityType::Circle(circle) => {
let mut ellipse = Ellipse {
common: circle.common.clone(),
center: circle.center,
major_axis: Vector3::UNIT_X * circle.radius,
minor_axis_ratio: 1.0,
start_parameter: 0.0,
end_parameter: std::f64::consts::TAU,
normal: circle.normal,
};
Self::apply_full_ellipse_transform(&mut ellipse, transform);
self.resolve_properties(&mut ellipse.common);
Some(EntityType::Ellipse(ellipse))
}
_ => {
let mut cloned = entity.clone();
cloned.as_entity_mut().apply_transform(transform);
self.resolve_properties(cloned.common_mut());
Some(cloned)
}
}
}
fn explode_arc_uniform(&self, arc: &Arc, transform: &Transform) -> EntityType {
let start = arc.start_point();
let end = arc.end_point();
let new_center = transform.apply(arc.center);
let new_start = transform.apply(start);
let new_end = transform.apply(end);
let new_radius = (new_start - new_center).length();
let ds = new_start - new_center;
let de = new_end - new_center;
let new_start_angle = ds.y.atan2(ds.x);
let new_end_angle = de.y.atan2(de.x);
let mut new_arc =
Arc::from_center_radius_angles(new_center, new_radius, new_start_angle, new_end_angle);
new_arc.normal = Self::transform_normal(transform, arc.normal);
new_arc.common = arc.common.clone();
self.resolve_properties(&mut new_arc.common);
EntityType::Arc(new_arc)
}
fn explode_arc_to_ellipse(&self, arc: &Arc, transform: &Transform) -> EntityType {
let mut ellipse = Ellipse {
common: arc.common.clone(),
center: arc.center,
major_axis: Vector3::UNIT_X * arc.radius,
minor_axis_ratio: 1.0,
start_parameter: arc.start_angle,
end_parameter: arc.end_angle,
normal: arc.normal,
};
Self::apply_full_ellipse_transform(&mut ellipse, transform);
self.resolve_properties(&mut ellipse.common);
EntityType::Ellipse(ellipse)
}
fn apply_full_ellipse_transform(ellipse: &mut Ellipse, transform: &Transform) {
let original_minor_dir = ellipse.normal.cross(&ellipse.major_axis).normalize();
let original_minor_len = ellipse.major_axis.length() * ellipse.minor_axis_ratio;
let original_minor = original_minor_dir * original_minor_len;
ellipse.center = transform.apply(ellipse.center);
let new_major = transform.apply_rotation(ellipse.major_axis);
let new_minor = transform.apply_rotation(original_minor);
let new_major_len = new_major.length();
let new_minor_len = new_minor.length();
if new_minor_len > new_major_len + 1e-12 {
ellipse.major_axis = new_minor;
ellipse.minor_axis_ratio = new_major_len / new_minor_len;
ellipse.start_parameter -= std::f64::consts::FRAC_PI_2;
ellipse.end_parameter -= std::f64::consts::FRAC_PI_2;
} else {
ellipse.major_axis = new_major;
ellipse.minor_axis_ratio = if new_major_len > 1e-12 {
new_minor_len / new_major_len
} else {
1.0
};
}
ellipse.normal = Self::transform_normal(transform, ellipse.normal);
}
pub fn explode_from_document(&self, document: &crate::document::CadDocument) -> Vec<EntityType> {
match document.block_records.get(&self.block_name) {
Some(br) => {
let entities: Vec<EntityType> = br
.entity_handles
.iter()
.filter_map(|h| document.entity_index.get(h).map(|&idx| document.entities[idx].clone()))
.collect();
self.explode(&entities)
}
None => Vec::new(),
}
}
}
impl Entity for Insert {
fn handle(&self) -> Handle {
self.common.handle
}
fn set_handle(&mut self, handle: Handle) {
self.common.handle = handle;
}
fn layer(&self) -> &str {
&self.common.layer
}
fn set_layer(&mut self, layer: String) {
self.common.layer = layer;
}
fn color(&self) -> Color {
self.common.color
}
fn set_color(&mut self, color: Color) {
self.common.color = color;
}
fn line_weight(&self) -> LineWeight {
self.common.line_weight
}
fn set_line_weight(&mut self, weight: LineWeight) {
self.common.line_weight = weight;
}
fn transparency(&self) -> Transparency {
self.common.transparency
}
fn set_transparency(&mut self, transparency: Transparency) {
self.common.transparency = transparency;
}
fn is_invisible(&self) -> bool {
self.common.invisible
}
fn set_invisible(&mut self, invisible: bool) {
self.common.invisible = invisible;
}
fn bounding_box(&self) -> BoundingBox3D {
BoundingBox3D::from_point(self.insert_point)
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_insert(self, offset);
}
fn entity_type(&self) -> &'static str {
if self.is_minsert() { "MINSERT" } else { "INSERT" }
}
fn apply_transform(&mut self, transform: &Transform) {
super::transform::transform_insert(self, transform);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::entities::{
Block, BlockEnd, Circle, Line,
AttributeDefinition,
};
use std::f64::consts::{FRAC_PI_2, PI, TAU};
fn approx(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-6
}
fn approx_vec(a: Vector3, b: Vector3) -> bool {
approx(a.x, b.x) && approx(a.y, b.y) && approx(a.z, b.z)
}
#[test]
fn explode_empty_block_returns_empty() {
let insert = Insert::new("TestBlock", Vector3::ZERO);
let result = insert.explode(&[]);
assert!(result.is_empty());
}
#[test]
fn explode_skips_block_markers_and_attdefs() {
let block_entities = vec![
EntityType::Block(Block::new("TestBlock", Vector3::ZERO)),
EntityType::Line(Line::from_points(
Vector3::ZERO,
Vector3::new(1.0, 0.0, 0.0),
)),
EntityType::AttributeDefinition(AttributeDefinition::new(
"TAG".into(),
"prompt".into(),
"default".into(),
)),
EntityType::BlockEnd(BlockEnd::new()),
];
let insert = Insert::new("TestBlock", Vector3::ZERO);
let result = insert.explode(&block_entities);
assert_eq!(result.len(), 1);
assert!(matches!(result[0], EntityType::Line(_)));
}
#[test]
fn explode_identity_insert_preserves_line() {
let line = Line::from_points(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
);
let block_entities = vec![EntityType::Line(line)];
let insert = Insert::new("B", Vector3::ZERO);
let result = insert.explode(&block_entities);
assert_eq!(result.len(), 1);
if let EntityType::Line(l) = &result[0] {
assert!(approx_vec(l.start, Vector3::new(0.0, 0.0, 0.0)));
assert!(approx_vec(l.end, Vector3::new(10.0, 0.0, 0.0)));
} else {
panic!("expected Line");
}
}
#[test]
fn explode_with_translation() {
let line = Line::from_points(Vector3::ZERO, Vector3::new(5.0, 0.0, 0.0));
let block_entities = vec![EntityType::Line(line)];
let insert = Insert::new("B", Vector3::new(10.0, 20.0, 0.0));
let result = insert.explode(&block_entities);
if let EntityType::Line(l) = &result[0] {
assert!(approx_vec(l.start, Vector3::new(10.0, 20.0, 0.0)));
assert!(approx_vec(l.end, Vector3::new(15.0, 20.0, 0.0)));
} else {
panic!("expected Line");
}
}
#[test]
fn explode_with_uniform_scale() {
let line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
let block_entities = vec![EntityType::Line(line)];
let insert = Insert::new("B", Vector3::ZERO).with_uniform_scale(3.0);
let result = insert.explode(&block_entities);
if let EntityType::Line(l) = &result[0] {
assert!(approx_vec(l.end, Vector3::new(3.0, 0.0, 0.0)));
} else {
panic!("expected Line");
}
}
#[test]
fn explode_with_90_degree_rotation() {
let line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
let block_entities = vec![EntityType::Line(line)];
let insert = Insert::new("B", Vector3::ZERO).with_rotation(FRAC_PI_2);
let result = insert.explode(&block_entities);
if let EntityType::Line(l) = &result[0] {
assert!(approx_vec(l.end, Vector3::new(0.0, 1.0, 0.0)));
} else {
panic!("expected Line");
}
}
#[test]
fn explode_with_scale_and_translation() {
let line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 1.0, 0.0));
let block_entities = vec![EntityType::Line(line)];
let insert = Insert::new("B", Vector3::new(5.0, 5.0, 0.0))
.with_scale(2.0, 3.0, 1.0);
let result = insert.explode(&block_entities);
if let EntityType::Line(l) = &result[0] {
assert!(approx_vec(l.start, Vector3::new(5.0, 5.0, 0.0)));
assert!(approx_vec(l.end, Vector3::new(7.0, 8.0, 0.0)));
} else {
panic!("expected Line");
}
}
#[test]
fn explode_circle_becomes_ellipse() {
let circle = Circle::from_center_radius(Vector3::ZERO, 5.0);
let block_entities = vec![EntityType::Circle(circle)];
let insert = Insert::new("B", Vector3::ZERO);
let result = insert.explode(&block_entities);
assert_eq!(result.len(), 1);
assert!(matches!(result[0], EntityType::Ellipse(_)));
if let EntityType::Ellipse(e) = &result[0] {
assert!(approx(e.start_parameter, 0.0));
assert!(approx(e.end_parameter, TAU));
} else {
panic!("expected Ellipse");
}
}
#[test]
fn explode_circle_with_non_uniform_scale() {
let circle = Circle::from_center_radius(Vector3::ZERO, 1.0);
let block_entities = vec![EntityType::Circle(circle)];
let insert = Insert::new("B", Vector3::ZERO).with_scale(2.0, 1.0, 1.0);
let result = insert.explode(&block_entities);
if let EntityType::Ellipse(e) = &result[0] {
let major_len = e.major_axis.length();
let minor_len = major_len * e.minor_axis_ratio;
assert!(approx(major_len, 2.0));
assert!(approx(minor_len, 1.0));
assert!(approx(e.minor_axis_ratio, 0.5));
} else {
panic!("expected Ellipse");
}
}
#[test]
fn explode_circle_non_uniform_scale_minor_becomes_major() {
let circle = Circle::from_center_radius(Vector3::ZERO, 1.0);
let block_entities = vec![EntityType::Circle(circle)];
let insert = Insert::new("B", Vector3::ZERO).with_scale(1.0, 3.0, 1.0);
let result = insert.explode(&block_entities);
if let EntityType::Ellipse(e) = &result[0] {
let major_len = e.major_axis.length();
let minor_len = major_len * e.minor_axis_ratio;
assert!(approx(major_len, 3.0));
assert!(approx(minor_len, 1.0));
} else {
panic!("expected Ellipse");
}
}
#[test]
fn explode_arc_identity() {
let arc = Arc::from_center_radius_angles(
Vector3::ZERO,
5.0,
0.0,
FRAC_PI_2,
);
let block_entities = vec![EntityType::Arc(arc.clone())];
let insert = Insert::new("B", Vector3::ZERO);
let result = insert.explode(&block_entities);
assert_eq!(result.len(), 1);
if let EntityType::Arc(a) = &result[0] {
assert!(approx(a.radius, 5.0));
assert!(approx(a.start_angle, 0.0));
assert!(approx(a.end_angle, FRAC_PI_2));
assert!(approx_vec(a.center, Vector3::ZERO));
} else {
panic!("expected Arc");
}
}
#[test]
fn explode_arc_with_translation() {
let arc = Arc::from_center_radius_angles(
Vector3::ZERO,
10.0,
0.0,
PI,
);
let block_entities = vec![EntityType::Arc(arc)];
let insert = Insert::new("B", Vector3::new(100.0, 200.0, 0.0));
let result = insert.explode(&block_entities);
if let EntityType::Arc(a) = &result[0] {
assert!(approx_vec(a.center, Vector3::new(100.0, 200.0, 0.0)));
assert!(approx(a.radius, 10.0));
} else {
panic!("expected Arc");
}
}
#[test]
fn explode_arc_with_uniform_scale() {
let arc = Arc::from_center_radius_angles(
Vector3::new(1.0, 1.0, 0.0),
2.0,
0.0,
FRAC_PI_2,
);
let block_entities = vec![EntityType::Arc(arc)];
let insert = Insert::new("B", Vector3::ZERO).with_uniform_scale(3.0);
let result = insert.explode(&block_entities);
if let EntityType::Arc(a) = &result[0] {
assert!(approx(a.radius, 6.0)); assert!(approx_vec(a.center, Vector3::new(3.0, 3.0, 0.0)));
} else {
panic!("expected Arc");
}
}
#[test]
fn explode_arc_non_uniform_becomes_ellipse() {
let arc = Arc::from_center_radius_angles(
Vector3::ZERO,
5.0,
0.0,
FRAC_PI_2,
);
let block_entities = vec![EntityType::Arc(arc)];
let insert = Insert::new("B", Vector3::ZERO).with_scale(2.0, 1.0, 1.0);
let result = insert.explode(&block_entities);
assert_eq!(result.len(), 1);
if let EntityType::Ellipse(e) = &result[0] {
let major_len = e.major_axis.length();
let minor_len = major_len * e.minor_axis_ratio;
assert!(approx(major_len, 10.0));
assert!(approx(minor_len, 5.0));
assert!(!e.is_full());
} else {
panic!("expected Ellipse for non-uniform arc");
}
}
#[test]
fn explode_multiple_entities() {
let block_entities = vec![
EntityType::Line(Line::from_points(
Vector3::ZERO,
Vector3::new(1.0, 0.0, 0.0),
)),
EntityType::Circle(Circle::from_center_radius(Vector3::ZERO, 1.0)),
EntityType::Arc(Arc::from_center_radius_angles(
Vector3::ZERO, 1.0, 0.0, PI,
)),
];
let insert = Insert::new("B", Vector3::new(10.0, 0.0, 0.0));
let result = insert.explode(&block_entities);
assert_eq!(result.len(), 3);
assert!(matches!(result[0], EntityType::Line(_)));
assert!(matches!(result[1], EntityType::Ellipse(_))); assert!(matches!(result[2], EntityType::Arc(_))); }
#[test]
fn explode_layer_zero_inherits_insert_layer() {
let mut line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
line.common.layer = "0".to_string(); let block_entities = vec![EntityType::Line(line)];
let mut insert = Insert::new("B", Vector3::ZERO);
insert.common.layer = "Walls".to_string();
let result = insert.explode(&block_entities);
assert_eq!(result[0].common().layer, "Walls");
}
#[test]
fn explode_named_layer_preserved() {
let mut line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
line.common.layer = "Hidden".to_string();
let block_entities = vec![EntityType::Line(line)];
let mut insert = Insert::new("B", Vector3::ZERO);
insert.common.layer = "Walls".to_string();
let result = insert.explode(&block_entities);
assert_eq!(result[0].common().layer, "Hidden");
}
#[test]
fn explode_byblock_color_inherits_insert_color() {
let mut line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
line.common.color = Color::ByBlock;
let block_entities = vec![EntityType::Line(line)];
let mut insert = Insert::new("B", Vector3::ZERO);
insert.common.color = Color::from_index(1); let result = insert.explode(&block_entities);
assert_eq!(result[0].common().color, Color::from_index(1));
}
#[test]
fn explode_byblock_lineweight_inherits_insert_lineweight() {
let mut line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
line.common.line_weight = LineWeight::ByBlock;
let block_entities = vec![EntityType::Line(line)];
let mut insert = Insert::new("B", Vector3::ZERO);
insert.common.line_weight = LineWeight::from_value(50); let result = insert.explode(&block_entities);
assert_eq!(result[0].common().line_weight, LineWeight::from_value(50));
}
#[test]
fn explode_non_byblock_color_preserved() {
let mut line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
line.common.color = Color::from_index(3); let block_entities = vec![EntityType::Line(line)];
let mut insert = Insert::new("B", Vector3::ZERO);
insert.common.color = Color::from_index(1); let result = insert.explode(&block_entities);
assert_eq!(result[0].common().color, Color::from_index(3));
}
#[test]
fn explode_minsert_produces_array_copies() {
let line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
let block_entities = vec![EntityType::Line(line)];
let insert = Insert::new("B", Vector3::new(0.0, 0.0, 0.0))
.with_array(2, 3, 10.0, 20.0);
let result = insert.explode(&block_entities);
assert_eq!(result.len(), 6);
for e in &result {
assert!(matches!(e, EntityType::Line(_)));
}
}
#[test]
fn explode_minsert_positions_correct() {
let line = Line::from_points(Vector3::ZERO, Vector3::ZERO); let block_entities = vec![EntityType::Line(line)];
let insert = Insert::new("B", Vector3::new(5.0, 0.0, 0.0))
.with_array(2, 1, 10.0, 0.0);
let result = insert.explode(&block_entities);
assert_eq!(result.len(), 2);
if let EntityType::Line(l0) = &result[0] {
assert!(approx_vec(l0.start, Vector3::new(5.0, 0.0, 0.0)));
}
if let EntityType::Line(l1) = &result[1] {
assert!(approx_vec(l1.start, Vector3::new(15.0, 0.0, 0.0)));
}
}
#[test]
fn explode_arc_layer_zero_inherits() {
let mut arc = Arc::from_center_radius_angles(Vector3::ZERO, 1.0, 0.0, PI);
arc.common.layer = "0".to_string();
let block_entities = vec![EntityType::Arc(arc)];
let mut insert = Insert::new("B", Vector3::ZERO);
insert.common.layer = "Pipes".to_string();
let result = insert.explode(&block_entities);
assert_eq!(result[0].common().layer, "Pipes");
}
#[test]
fn explode_circle_byblock_color_inherits() {
let mut circle = Circle::from_center_radius(Vector3::ZERO, 1.0);
circle.common.color = Color::ByBlock;
let block_entities = vec![EntityType::Circle(circle)];
let mut insert = Insert::new("B", Vector3::ZERO);
insert.common.color = Color::from_index(5); let result = insert.explode(&block_entities);
if let EntityType::Ellipse(e) = &result[0] {
assert_eq!(e.common.color, Color::from_index(5));
} else {
panic!("expected Ellipse");
}
}
#[test]
fn explode_preserves_entity_layer() {
let mut line = Line::from_points(Vector3::ZERO, Vector3::new(1.0, 0.0, 0.0));
line.common.layer = "MyLayer".to_string();
let block_entities = vec![EntityType::Line(line)];
let insert = Insert::new("B", Vector3::ZERO);
let result = insert.explode(&block_entities);
assert_eq!(result[0].common().layer, "MyLayer");
}
#[test]
fn explode_arc_preserves_layer() {
let mut arc = Arc::from_center_radius_angles(Vector3::ZERO, 1.0, 0.0, PI);
arc.common.layer = "ArcLayer".to_string();
let block_entities = vec![EntityType::Arc(arc)];
let insert = Insert::new("B", Vector3::ZERO);
let result = insert.explode(&block_entities);
if let EntityType::Arc(a) = &result[0] {
assert_eq!(a.common.layer, "ArcLayer");
} else {
panic!("expected Arc");
}
}
}