use crate::entities::{Entity, EntityCommon};
use crate::types::{BoundingBox3D, Color, Handle, LineWeight, Transparency, Vector2, Vector3};
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UnderlayDisplayFlags: u8 {
const NONE = 0;
const CLIPPING = 1;
const ON = 2;
const MONOCHROME = 4;
const ADJUST_FOR_BACKGROUND = 8;
const CLIP_INSIDE = 16;
const DEFAULT = Self::ON.bits();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UnderlayType {
#[default]
Pdf,
Dwf,
Dgn,
}
impl UnderlayType {
pub fn entity_name(&self) -> &'static str {
match self {
UnderlayType::Pdf => "PDFUNDERLAY",
UnderlayType::Dwf => "DWFUNDERLAY",
UnderlayType::Dgn => "DGNUNDERLAY",
}
}
pub fn definition_name(&self) -> &'static str {
match self {
UnderlayType::Pdf => "PDFDEFINITION",
UnderlayType::Dwf => "DWFDEFINITION",
UnderlayType::Dgn => "DGNDEFINITION",
}
}
pub fn subclass_marker(&self) -> &'static str {
"AcDbUnderlayReference"
}
pub fn definition_subclass_marker(&self) -> &'static str {
match self {
UnderlayType::Pdf => "AcDbPdfDefinition",
UnderlayType::Dwf => "AcDbDwfDefinition",
UnderlayType::Dgn => "AcDbDgnDefinition",
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UnderlayDefinition {
pub handle: Handle,
pub owner_handle: Handle,
pub underlay_type: UnderlayType,
pub file_path: String,
pub page_name: String,
pub name: String,
pub reactors: Vec<Handle>,
}
impl UnderlayDefinition {
pub fn new(underlay_type: UnderlayType) -> Self {
UnderlayDefinition {
handle: Handle::NULL,
owner_handle: Handle::NULL,
underlay_type,
file_path: String::new(),
page_name: String::new(),
name: String::new(),
reactors: Vec::new(),
}
}
pub fn pdf(file_path: &str, page_name: &str) -> Self {
UnderlayDefinition {
underlay_type: UnderlayType::Pdf,
file_path: file_path.to_string(),
page_name: page_name.to_string(),
..Self::new(UnderlayType::Pdf)
}
}
pub fn dwf(file_path: &str, sheet_name: &str) -> Self {
UnderlayDefinition {
underlay_type: UnderlayType::Dwf,
file_path: file_path.to_string(),
page_name: sheet_name.to_string(),
..Self::new(UnderlayType::Dwf)
}
}
pub fn dgn(file_path: &str, model_name: &str) -> Self {
UnderlayDefinition {
underlay_type: UnderlayType::Dgn,
file_path: file_path.to_string(),
page_name: model_name.to_string(),
..Self::new(UnderlayType::Dgn)
}
}
pub fn entity_name(&self) -> &'static str {
self.underlay_type.definition_name()
}
pub fn subclass_marker(&self) -> &'static str {
self.underlay_type.definition_subclass_marker()
}
pub fn file_exists(&self) -> bool {
std::path::Path::new(&self.file_path).exists()
}
pub fn file_extension(&self) -> Option<&str> {
std::path::Path::new(&self.file_path)
.extension()
.and_then(|s| s.to_str())
}
}
impl Default for UnderlayDefinition {
fn default() -> Self {
Self::new(UnderlayType::Pdf)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Underlay {
pub common: EntityCommon,
pub underlay_type: UnderlayType,
pub definition_handle: Handle,
pub insertion_point: Vector3,
pub x_scale: f64,
pub y_scale: f64,
pub z_scale: f64,
pub rotation: f64,
pub normal: Vector3,
pub flags: UnderlayDisplayFlags,
pub contrast: u8,
pub fade: u8,
pub clip_boundary_vertices: Vec<Vector2>,
pub clip_inverted: bool,
}
impl Underlay {
pub const ENTITY_NAME: &'static str = "UNDERLAY";
pub const SUBCLASS_MARKER: &'static str = "AcDbUnderlayReference";
pub fn new(underlay_type: UnderlayType) -> Self {
Underlay {
common: EntityCommon::default(),
underlay_type,
definition_handle: Handle::NULL,
insertion_point: Vector3::ZERO,
x_scale: 1.0,
y_scale: 1.0,
z_scale: 1.0,
rotation: 0.0,
normal: Vector3::UNIT_Z,
flags: UnderlayDisplayFlags::DEFAULT,
contrast: 100,
fade: 0,
clip_boundary_vertices: Vec::new(),
clip_inverted: false,
}
}
pub fn pdf() -> Self {
Self::new(UnderlayType::Pdf)
}
pub fn dwf() -> Self {
Self::new(UnderlayType::Dwf)
}
pub fn dgn() -> Self {
Self::new(UnderlayType::Dgn)
}
pub fn at_point(underlay_type: UnderlayType, point: Vector3) -> Self {
Underlay {
insertion_point: point,
..Self::new(underlay_type)
}
}
pub fn with_scale(
underlay_type: UnderlayType,
point: Vector3,
scale: f64,
) -> Self {
Underlay {
insertion_point: point,
x_scale: scale,
y_scale: scale,
z_scale: scale,
..Self::new(underlay_type)
}
}
pub fn entity_name(&self) -> &'static str {
self.underlay_type.entity_name()
}
pub fn subclass_marker(&self) -> &'static str {
Self::SUBCLASS_MARKER
}
pub fn set_scale(&mut self, scale: f64) {
self.x_scale = scale;
self.y_scale = scale;
self.z_scale = scale;
}
pub fn set_scale_xyz(&mut self, x: f64, y: f64, z: f64) {
self.x_scale = x;
self.y_scale = y;
self.z_scale = z;
}
pub fn uniform_scale(&self) -> Option<f64> {
if (self.x_scale - self.y_scale).abs() < 1e-10
&& (self.y_scale - self.z_scale).abs() < 1e-10
{
Some(self.x_scale)
} else {
None
}
}
pub fn set_rotation_degrees(&mut self, degrees: f64) {
self.rotation = degrees.to_radians();
}
pub fn rotation_degrees(&self) -> f64 {
self.rotation.to_degrees()
}
pub fn set_rectangular_clip(&mut self, min: Vector2, max: Vector2) {
self.clip_boundary_vertices = vec![
min,
Vector2::new(max.x, min.y),
max,
Vector2::new(min.x, max.y),
];
self.flags |= UnderlayDisplayFlags::CLIPPING;
}
pub fn set_polygon_clip(&mut self, vertices: &[Vector2]) {
if vertices.len() >= 3 {
self.clip_boundary_vertices = vertices.to_vec();
self.flags |= UnderlayDisplayFlags::CLIPPING;
}
}
pub fn clear_clip(&mut self) {
self.clip_boundary_vertices.clear();
self.flags -= UnderlayDisplayFlags::CLIPPING;
}
pub fn is_clipping(&self) -> bool {
self.flags.contains(UnderlayDisplayFlags::CLIPPING)
}
pub fn is_on(&self) -> bool {
self.flags.contains(UnderlayDisplayFlags::ON)
}
pub fn set_on(&mut self, on: bool) {
if on {
self.flags |= UnderlayDisplayFlags::ON;
} else {
self.flags -= UnderlayDisplayFlags::ON;
}
}
pub fn is_monochrome(&self) -> bool {
self.flags.contains(UnderlayDisplayFlags::MONOCHROME)
}
pub fn set_monochrome(&mut self, monochrome: bool) {
if monochrome {
self.flags |= UnderlayDisplayFlags::MONOCHROME;
} else {
self.flags -= UnderlayDisplayFlags::MONOCHROME;
}
}
pub fn set_contrast(&mut self, value: u8) {
self.contrast = value.min(100);
}
pub fn set_fade(&mut self, value: u8) {
self.fade = value.min(80);
}
pub fn world_clip_boundary(&self) -> Vec<Vector3> {
let cos_r = self.rotation.cos();
let sin_r = self.rotation.sin();
self.clip_boundary_vertices
.iter()
.map(|v| {
let x = v.x * self.x_scale;
let y = v.y * self.y_scale;
let rx = x * cos_r - y * sin_r;
let ry = x * sin_r + y * cos_r;
self.insertion_point + Vector3::new(rx, ry, 0.0)
})
.collect()
}
pub fn clip_vertex_count(&self) -> usize {
self.clip_boundary_vertices.len()
}
}
impl Default for Underlay {
fn default() -> Self {
Self::new(UnderlayType::Pdf)
}
}
impl Entity for Underlay {
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 {
if !self.clip_boundary_vertices.is_empty() {
let world_verts = self.world_clip_boundary();
let mut min = world_verts[0];
let mut max = world_verts[0];
for v in &world_verts[1..] {
min.x = min.x.min(v.x);
min.y = min.y.min(v.y);
min.z = min.z.min(v.z);
max.x = max.x.max(v.x);
max.y = max.y.max(v.y);
max.z = max.z.max(v.z);
}
BoundingBox3D::new(min, max)
} else {
BoundingBox3D::new(self.insertion_point, self.insertion_point)
}
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_underlay(self, offset);
}
fn entity_type(&self) -> &'static str {
self.underlay_type.entity_name()
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_underlay(self, transform);
}
}
pub type PdfUnderlay = Underlay;
pub type DwfUnderlay = Underlay;
pub type DgnUnderlay = Underlay;
pub type PdfUnderlayDefinition = UnderlayDefinition;
pub type DwfUnderlayDefinition = UnderlayDefinition;
pub type DgnUnderlayDefinition = UnderlayDefinition;
impl Underlay {
pub fn pdf_at(point: Vector3) -> Self {
Self::at_point(UnderlayType::Pdf, point)
}
pub fn dwf_at(point: Vector3) -> Self {
Self::at_point(UnderlayType::Dwf, point)
}
pub fn dgn_at(point: Vector3) -> Self {
Self::at_point(UnderlayType::Dgn, point)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_underlay_definition_creation() {
let def = UnderlayDefinition::new(UnderlayType::Pdf);
assert_eq!(def.underlay_type, UnderlayType::Pdf);
assert!(def.file_path.is_empty());
}
#[test]
fn test_pdf_definition() {
let def = UnderlayDefinition::pdf("drawings/floor_plan.pdf", "Page 1");
assert_eq!(def.underlay_type, UnderlayType::Pdf);
assert_eq!(def.file_path, "drawings/floor_plan.pdf");
assert_eq!(def.page_name, "Page 1");
}
#[test]
fn test_dwf_definition() {
let def = UnderlayDefinition::dwf("drawings/assembly.dwf", "Sheet 1");
assert_eq!(def.underlay_type, UnderlayType::Dwf);
assert_eq!(def.file_path, "drawings/assembly.dwf");
}
#[test]
fn test_dgn_definition() {
let def = UnderlayDefinition::dgn("drawings/site.dgn", "Default");
assert_eq!(def.underlay_type, UnderlayType::Dgn);
assert_eq!(def.file_path, "drawings/site.dgn");
}
#[test]
fn test_definition_entity_names() {
let pdf_def = UnderlayDefinition::new(UnderlayType::Pdf);
assert_eq!(pdf_def.entity_name(), "PDFDEFINITION");
let dwf_def = UnderlayDefinition::new(UnderlayType::Dwf);
assert_eq!(dwf_def.entity_name(), "DWFDEFINITION");
let dgn_def = UnderlayDefinition::new(UnderlayType::Dgn);
assert_eq!(dgn_def.entity_name(), "DGNDEFINITION");
}
#[test]
fn test_underlay_creation() {
let underlay = Underlay::new(UnderlayType::Pdf);
assert_eq!(underlay.underlay_type, UnderlayType::Pdf);
assert_eq!(underlay.insertion_point, Vector3::ZERO);
assert_eq!(underlay.x_scale, 1.0);
assert_eq!(underlay.y_scale, 1.0);
assert_eq!(underlay.z_scale, 1.0);
}
#[test]
fn test_pdf_underlay() {
let underlay = Underlay::pdf();
assert_eq!(underlay.underlay_type, UnderlayType::Pdf);
assert_eq!(underlay.entity_name(), "PDFUNDERLAY");
}
#[test]
fn test_dwf_underlay() {
let underlay = Underlay::dwf();
assert_eq!(underlay.underlay_type, UnderlayType::Dwf);
assert_eq!(underlay.entity_name(), "DWFUNDERLAY");
}
#[test]
fn test_dgn_underlay() {
let underlay = Underlay::dgn();
assert_eq!(underlay.underlay_type, UnderlayType::Dgn);
assert_eq!(underlay.entity_name(), "DGNUNDERLAY");
}
#[test]
fn test_at_point() {
let underlay = Underlay::pdf_at(Vector3::new(10.0, 20.0, 0.0));
assert_eq!(underlay.insertion_point.x, 10.0);
assert_eq!(underlay.insertion_point.y, 20.0);
}
#[test]
fn test_with_scale() {
let underlay = Underlay::with_scale(
UnderlayType::Pdf,
Vector3::ZERO,
2.5,
);
assert_eq!(underlay.x_scale, 2.5);
assert_eq!(underlay.y_scale, 2.5);
assert_eq!(underlay.z_scale, 2.5);
}
#[test]
fn test_set_scale() {
let mut underlay = Underlay::pdf();
underlay.set_scale(3.0);
assert_eq!(underlay.x_scale, 3.0);
assert_eq!(underlay.y_scale, 3.0);
assert_eq!(underlay.z_scale, 3.0);
}
#[test]
fn test_set_scale_xyz() {
let mut underlay = Underlay::pdf();
underlay.set_scale_xyz(1.0, 2.0, 3.0);
assert_eq!(underlay.x_scale, 1.0);
assert_eq!(underlay.y_scale, 2.0);
assert_eq!(underlay.z_scale, 3.0);
}
#[test]
fn test_uniform_scale() {
let mut underlay = Underlay::pdf();
underlay.set_scale(2.0);
assert_eq!(underlay.uniform_scale(), Some(2.0));
underlay.set_scale_xyz(1.0, 2.0, 1.0);
assert_eq!(underlay.uniform_scale(), None);
}
#[test]
fn test_rotation() {
let mut underlay = Underlay::pdf();
underlay.set_rotation_degrees(45.0);
assert!((underlay.rotation_degrees() - 45.0).abs() < 1e-10);
}
#[test]
fn test_rectangular_clip() {
let mut underlay = Underlay::pdf();
underlay.set_rectangular_clip(
Vector2::new(0.0, 0.0),
Vector2::new(100.0, 100.0),
);
assert_eq!(underlay.clip_vertex_count(), 4);
assert!(underlay.is_clipping());
}
#[test]
fn test_polygon_clip() {
let mut underlay = Underlay::pdf();
underlay.set_polygon_clip(&[
Vector2::new(0.0, 0.0),
Vector2::new(100.0, 0.0),
Vector2::new(100.0, 100.0),
Vector2::new(50.0, 150.0),
Vector2::new(0.0, 100.0),
]);
assert_eq!(underlay.clip_vertex_count(), 5);
assert!(underlay.is_clipping());
}
#[test]
fn test_clear_clip() {
let mut underlay = Underlay::pdf();
underlay.set_rectangular_clip(
Vector2::new(0.0, 0.0),
Vector2::new(100.0, 100.0),
);
assert!(underlay.is_clipping());
underlay.clear_clip();
assert!(!underlay.is_clipping());
assert_eq!(underlay.clip_vertex_count(), 0);
}
#[test]
fn test_visibility() {
let mut underlay = Underlay::pdf();
assert!(underlay.is_on());
underlay.set_on(false);
assert!(!underlay.is_on());
underlay.set_on(true);
assert!(underlay.is_on());
}
#[test]
fn test_monochrome() {
let mut underlay = Underlay::pdf();
assert!(!underlay.is_monochrome());
underlay.set_monochrome(true);
assert!(underlay.is_monochrome());
}
#[test]
fn test_contrast_fade() {
let mut underlay = Underlay::pdf();
underlay.set_contrast(75);
assert_eq!(underlay.contrast, 75);
underlay.set_contrast(150); assert_eq!(underlay.contrast, 100);
underlay.set_fade(50);
assert_eq!(underlay.fade, 50);
underlay.set_fade(100); assert_eq!(underlay.fade, 80);
}
#[test]
fn test_translate() {
let mut underlay = Underlay::pdf();
underlay.translate(Vector3::new(10.0, 20.0, 30.0));
assert_eq!(underlay.insertion_point.x, 10.0);
assert_eq!(underlay.insertion_point.y, 20.0);
assert_eq!(underlay.insertion_point.z, 30.0);
}
#[test]
fn test_entity_type() {
let pdf = Underlay::pdf();
assert_eq!(pdf.entity_type(), "PDFUNDERLAY");
let dwf = Underlay::dwf();
assert_eq!(dwf.entity_type(), "DWFUNDERLAY");
let dgn = Underlay::dgn();
assert_eq!(dgn.entity_type(), "DGNUNDERLAY");
}
#[test]
fn test_world_clip_boundary() {
let mut underlay = Underlay::pdf();
underlay.insertion_point = Vector3::new(10.0, 10.0, 0.0);
underlay.x_scale = 2.0;
underlay.y_scale = 2.0;
underlay.rotation = 0.0;
underlay.set_rectangular_clip(
Vector2::new(0.0, 0.0),
Vector2::new(5.0, 5.0),
);
let world_verts = underlay.world_clip_boundary();
assert_eq!(world_verts.len(), 4);
assert!((world_verts[0].x - 10.0).abs() < 1e-10);
assert!((world_verts[0].y - 10.0).abs() < 1e-10);
}
#[test]
fn test_underlay_type_names() {
assert_eq!(UnderlayType::Pdf.entity_name(), "PDFUNDERLAY");
assert_eq!(UnderlayType::Dwf.entity_name(), "DWFUNDERLAY");
assert_eq!(UnderlayType::Dgn.entity_name(), "DGNUNDERLAY");
}
#[test]
fn test_bounding_box_with_clip() {
let mut underlay = Underlay::pdf();
underlay.insertion_point = Vector3::new(10.0, 10.0, 0.0);
underlay.set_rectangular_clip(
Vector2::new(0.0, 0.0),
Vector2::new(100.0, 50.0),
);
let bb = underlay.bounding_box();
assert!((bb.min.x - 10.0).abs() < 1e-10);
assert!((bb.min.y - 10.0).abs() < 1e-10);
assert!((bb.max.x - 110.0).abs() < 1e-10);
assert!((bb.max.y - 60.0).abs() < 1e-10);
}
#[test]
fn test_flags() {
let underlay = Underlay::pdf();
assert!(underlay.flags.contains(UnderlayDisplayFlags::ON));
assert!(!underlay.flags.contains(UnderlayDisplayFlags::CLIPPING));
assert!(!underlay.flags.contains(UnderlayDisplayFlags::MONOCHROME));
}
#[test]
fn test_default() {
let underlay = Underlay::default();
assert_eq!(underlay.underlay_type, UnderlayType::Pdf);
}
}