use serde::{Serialize, Deserialize};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::fmt;
use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SheetCategory {
Architectural,
Structural,
Mechanical,
Electrical,
Plumbing,
Civil,
General,
Custom(String),
}
impl Default for SheetCategory {
fn default() -> Self {
SheetCategory::General
}
}
impl SheetCategory {
pub fn name(&self) -> &str {
match self {
SheetCategory::Architectural => "Architectural",
SheetCategory::Structural => "Structural",
SheetCategory::Mechanical => "Mechanical",
SheetCategory::Electrical => "Electrical",
SheetCategory::Plumbing => "Plumbing",
SheetCategory::Civil => "Civil",
SheetCategory::General => "General",
SheetCategory::Custom(name) => name.as_str(),
}
}
pub fn icon(&self) -> &str {
match self {
SheetCategory::Architectural => "🏛️",
SheetCategory::Structural => "🏗️",
SheetCategory::Mechanical => "⚙️",
SheetCategory::Electrical => "⚡",
SheetCategory::Plumbing => "🚿",
SheetCategory::Civil => "🛣️",
SheetCategory::General => "📄",
SheetCategory::Custom(_) => "📁",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SheetStatus {
NotStarted,
InProgress,
ForReview,
IssuedForConstruction,
Revised,
Archived,
Void,
}
impl Default for SheetStatus {
fn default() -> Self {
SheetStatus::NotStarted
}
}
impl SheetStatus {
pub fn name(&self) -> &str {
match self {
SheetStatus::NotStarted => "Not Started",
SheetStatus::InProgress => "In Progress",
SheetStatus::ForReview => "For Review",
SheetStatus::IssuedForConstruction => "Issued for Construction",
SheetStatus::Revised => "Revised",
SheetStatus::Archived => "Archived",
SheetStatus::Void => "Void",
}
}
pub fn color(&self) -> (u8, u8, u8) {
match self {
SheetStatus::NotStarted => (128, 128, 128),
SheetStatus::InProgress => (255, 255, 0),
SheetStatus::ForReview => (255, 165, 0),
SheetStatus::IssuedForConstruction => (0, 128, 0),
SheetStatus::Revised => (0, 0, 255),
SheetStatus::Archived => (128, 128, 128),
SheetStatus::Void => (255, 0, 0),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SheetProperties {
pub title: String,
pub number: String,
pub revision: String,
pub category: SheetCategory,
pub status: SheetStatus,
pub description: String,
pub designer: String,
pub checker: String,
pub approver: String,
pub creation_date: SystemTime,
pub modification_date: SystemTime,
pub keywords: Vec<String>,
pub client: String,
pub project_name: String,
pub project_number: String,
pub drawing_file: PathBuf,
pub layout_name: String,
pub scale: f64,
pub page_setup: String,
print_settings: String,
publish_settings: String,
}
impl Default for SheetProperties {
fn default() -> Self {
Self::new()
}
}
impl SheetProperties {
pub fn new() -> Self {
Self {
title: String::new(),
number: String::new(),
revision: "A".to_string(),
category: SheetCategory::General,
status: SheetStatus::NotStarted,
description: String::new(),
designer: String::new(),
checker: String::new(),
approver: String::new(),
creation_date: SystemTime::UNIX_EPOCH,
modification_date: SystemTime::UNIX_EPOCH,
keywords: Vec::new(),
client: String::new(),
project_name: String::new(),
project_number: String::new(),
drawing_file: PathBuf::new(),
layout_name: String::new(),
scale: 1.0,
page_setup: String::new(),
print_settings: String::new(),
publish_settings: String::new(),
}
}
pub fn with_title(mut self, title: &str) -> Self {
self.title = title.to_string();
self
}
pub fn with_number(mut self, number: &str) -> Self {
self.number = number.to_string();
self
}
pub fn set_revision(&mut self, revision: &str) {
self.revision = revision.to_string();
}
pub fn increment_revision(&mut self) {
if self.revision.len() == 1 && self.revision.as_bytes()[0] >= b'A' && self.revision.as_bytes()[0] <= b'Z' {
let next = (self.revision.as_bytes()[0] + 1) as char;
self.revision = next.to_string();
} else if self.revision.starts_with('R') {
let num: i32 = self.revision[1..].parse().unwrap_or(0);
self.revision = format!("R{}", num + 1);
} else {
self.revision.push('1');
}
}
pub fn set_status(&mut self, status: SheetStatus) {
self.status = status;
}
pub fn add_keyword(&mut self, keyword: &str) {
if !self.keywords.contains(&keyword.to_string()) {
self.keywords.push(keyword.to_string());
}
}
pub fn set_designer(&mut self, designer: &str) {
self.designer = designer.to_string();
}
pub fn set_checker(&mut self, checker: &str) {
self.checker = checker.to_string();
}
pub fn set_approver(&mut self, approver: &str) {
self.approver = approver.to_string();
}
pub fn full_name(&self) -> String {
format!("{} - {}", self.number, self.title)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sheet {
pub id: String,
pub properties: SheetProperties,
pub sheet_subset: Option<String>,
pub layout_references: Vec<LayoutReference>,
pub xref_references: Vec<XrefSheetReference>,
pub block_references: Vec<BlockSheetReference>,
pub custom_data: HashMap<String, String>,
pub is_selected: bool,
pub is_locked: bool,
pub thumbnail: Option<Vec<u8>>,
pub annotations: Vec<SheetAnnotation>,
}
impl Default for Sheet {
fn default() -> Self {
Self::new()
}
}
impl Sheet {
pub fn new() -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
properties: SheetProperties::new(),
sheet_subset: None,
layout_references: Vec::new(),
xref_references: Vec::new(),
block_references: Vec::new(),
custom_data: HashMap::new(),
is_selected: false,
is_locked: false,
thumbnail: None,
annotations: Vec::new(),
}
}
pub fn with_properties(mut self, properties: SheetProperties) -> Self {
self.properties = properties;
self
}
pub fn add_layout_reference(&mut self, reference: LayoutReference) {
self.layout_references.push(reference);
}
pub fn add_xref_reference(&mut self, reference: XrefSheetReference) {
self.xref_references.push(reference);
}
pub fn add_block_reference(&mut self, reference: BlockSheetReference) {
self.block_references.push(reference);
}
pub fn set_custom_data(&mut self, key: &str, value: &str) {
self.custom_data.insert(key.to_string(), value.to_string());
}
pub fn get_custom_data(&self, key: &str) -> Option<&str> {
self.custom_data.get(key).map(|s| s.as_str())
}
pub fn select(&mut self) {
self.is_selected = true;
}
pub fn deselect(&mut self) {
self.is_selected = false;
}
pub fn lock(&mut self) {
self.is_locked = true;
}
pub fn unlock(&mut self) {
self.is_locked = false;
}
pub fn add_annotation(&mut self, annotation: SheetAnnotation) {
self.annotations.push(annotation);
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LayoutReference {
pub drawing_file: PathBuf,
pub layout_name: String,
pub is_required: bool,
}
impl Default for LayoutReference {
fn default() -> Self {
Self::new()
}
}
impl LayoutReference {
pub fn new() -> Self {
Self {
drawing_file: PathBuf::new(),
layout_name: String::new(),
is_required: true,
}
}
pub fn with_layout(mut self, drawing: &str, layout: &str) -> Self {
self.drawing_file = PathBuf::from(drawing);
self.layout_name = layout.to_string();
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct XrefSheetReference {
pub xref_name: String,
pub drawing_file: PathBuf,
pub attachment_point: AttachmentPoint,
pub is_required: bool,
}
impl Default for XrefSheetReference {
fn default() -> Self {
Self::new()
}
}
impl XrefSheetReference {
pub fn new() -> Self {
Self {
xref_name: String::new(),
drawing_file: PathBuf::new(),
attachment_point: AttachmentPoint::WorldCoordinateSystem,
is_required: true,
}
}
pub fn with_xref(mut self, name: &str, drawing: &str) -> Self {
self.xref_name = name.to_string();
self.drawing_file = PathBuf::from(drawing);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlockSheetReference {
pub block_name: String,
pub is_required: bool,
pub quantity: u32,
}
impl Default for BlockSheetReference {
fn default() -> Self {
Self::new()
}
}
impl BlockSheetReference {
pub fn new() -> Self {
Self {
block_name: String::new(),
is_required: true,
quantity: 1,
}
}
pub fn with_block(mut self, name: &str) -> Self {
self.block_name = name.to_string();
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SheetAnnotation {
pub id: String,
pub annotation_type: AnnotationType,
pub content: String,
pub position: (f64, f64),
pub date: SystemTime,
pub author: String,
}
impl Default for SheetAnnotation {
fn default() -> Self {
Self::new()
}
}
impl SheetAnnotation {
pub fn new() -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
annotation_type: AnnotationType::Note,
content: String::new(),
position: (0.0, 0.0),
date: SystemTime::UNIX_EPOCH,
author: String::new(),
}
}
pub fn with_content(mut self, content: &str) -> Self {
self.content = content.to_string();
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnnotationType {
Note,
Issue,
Revision,
Approval,
}
impl Default for AnnotationType {
fn default() -> Self {
AnnotationType::Note
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AttachmentPoint {
WorldCoordinateSystem,
CurrentUcs,
}
impl Default for AttachmentPoint {
fn default() -> Self {
AttachmentPoint::WorldCoordinateSystem
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SheetSubset {
pub id: String,
pub name: String,
pub description: String,
pub category: SheetCategory,
pub parent_id: Option<String>,
pub sheet_ids: Vec<String>,
pub subset_ids: Vec<String>,
pub properties: HashMap<String, String>,
pub display_order: i32,
}
impl Default for SheetSubset {
fn default() -> Self {
Self::new()
}
}
impl SheetSubset {
pub fn new() -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
name: String::new(),
description: String::new(),
category: SheetCategory::General,
parent_id: None,
sheet_ids: Vec::new(),
subset_ids: Vec::new(),
properties: HashMap::new(),
display_order: 0,
}
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
pub fn with_parent(mut self, parent_id: &str) -> Self {
self.parent_id = Some(parent_id.to_string());
self
}
pub fn add_sheet(&mut self, sheet_id: &str) {
if !self.sheet_ids.contains(&sheet_id.to_string()) {
self.sheet_ids.push(sheet_id.to_string());
}
}
pub fn remove_sheet(&mut self, sheet_id: &str) {
self.sheet_ids.retain(|id| id != sheet_id);
}
pub fn add_subset(&mut self, subset_id: &str) {
if !self.subset_ids.contains(&subset_id.to_string()) {
self.subset_ids.push(subset_id.to_string());
}
}
pub fn set_property(&mut self, key: &str, value: &str) {
self.properties.insert(key.to_string(), value.to_string());
}
pub fn sheet_count(&self) -> usize {
self.sheet_ids.len()
}
pub fn subset_count(&self) -> usize {
self.subset_ids.len()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SheetSetProperties {
pub name: String,
pub description: String,
pub project_number: String,
pub client_name: String,
pub designer: String,
pub creator: String,
pub creation_date: SystemTime,
pub modification_date: SystemTime,
pub default_category: SheetCategory,
pub default_units: String,
pub storage_location: PathBuf,
pub publish_destination: PathBuf,
pub is_read_only: bool,
pub is_archived: bool,
pub archive_date: Option<SystemTime>,
pub custom_data: HashMap<String, String>,
}
impl Default for SheetSetProperties {
fn default() -> Self {
Self::new()
}
}
impl SheetSetProperties {
pub fn new() -> Self {
Self {
name: String::new(),
description: String::new(),
project_number: String::new(),
client_name: String::new(),
designer: String::new(),
creator: String::new(),
creation_date: SystemTime::UNIX_EPOCH,
modification_date: SystemTime::UNIX_EPOCH,
default_category: SheetCategory::General,
default_units: "mm".to_string(),
storage_location: PathBuf::new(),
publish_destination: PathBuf::new(),
is_read_only: false,
is_archived: false,
archive_date: None,
custom_data: HashMap::new(),
}
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
pub fn set_project_number(&mut self, number: &str) {
self.project_number = number.to_string();
}
pub fn set_client_name(&mut self, client: &str) {
self.client_name = client.to_string();
}
pub fn set_storage_location(&mut self, path: &str) {
self.storage_location = PathBuf::from(path);
}
pub fn set_publish_destination(&mut self, path: &str) {
self.publish_destination = PathBuf::from(path);
}
pub fn set_read_only(&mut self, read_only: bool) {
self.is_read_only = read_only;
}
pub fn archive(&mut self) {
self.is_archived = true;
self.archive_date = Some(SystemTime::now());
}
pub fn unarchive(&mut self) {
self.is_archived = false;
self.archive_date = None;
}
pub fn set_custom_data(&mut self, key: &str, value: &str) {
self.custom_data.insert(key.to_string(), value.to_string());
}
}
#[derive(Debug, Clone)]
pub struct SheetSet {
pub properties: SheetSetProperties,
pub sheets: HashMap<String, Sheet>,
pub subsets: HashMap<String, SheetSubset>,
pub sheet_order: Vec<String>,
pub subset_order: Vec<String>,
pub resource_files: Vec<ResourceFile>,
pub templates: Vec<SheetTemplate>,
pub callout_blocks: Vec<CalloutBlock>,
pub revision_tables: Vec<RevisionTable>,
pub sheet_set_view_id: String,
pub is_modified: bool,
}
impl Default for SheetSet {
fn default() -> Self {
Self::new()
}
}
impl SheetSet {
pub fn new() -> Self {
Self {
properties: SheetSetProperties::new(),
sheets: HashMap::new(),
subsets: HashMap::new(),
sheet_order: Vec::new(),
subset_order: Vec::new(),
resource_files: Vec::new(),
templates: Vec::new(),
callout_blocks: Vec::new(),
revision_tables: Vec::new(),
sheet_set_view_id: String::new(),
is_modified: false,
}
}
pub fn with_properties(mut self, properties: SheetSetProperties) -> Self {
self.properties = properties;
self
}
pub fn create_sheet(&mut self, number: &str, title: &str) -> &mut Sheet {
let mut properties = SheetProperties::new()
.with_number(number)
.with_title(title);
properties.creation_date = SystemTime::now();
properties.modification_date = SystemTime::now();
let sheet = Sheet::with_properties(properties);
let sheet_id = sheet.id.clone();
self.sheets.insert(sheet_id.clone(), sheet);
self.sheet_order.push(sheet_id);
self.sheets.get_mut(&sheet_id).unwrap()
}
pub fn add_sheet(&mut self, sheet: Sheet) {
self.sheets.insert(sheet.id.clone(), sheet);
self.sheet_order.push(sheet.id.clone());
self.is_modified = true;
}
pub fn get_sheet(&self, id: &str) -> Option<&Sheet> {
self.sheets.get(id)
}
pub fn get_sheet_mut(&mut self, id: &str) -> Option<&mut Sheet> {
self.sheets.get_mut(id)
}
pub fn find_sheet_by_number(&self, number: &str) -> Option<&Sheet> {
self.sheets.values().find(|s| s.properties.number == number)
}
pub fn find_sheet_by_title(&self, title: &str) -> Option<&Sheet> {
self.sheets.values().find(|s| s.properties.title == title)
}
pub fn remove_sheet(&mut self, id: &str) -> bool {
if let Some(_sheet) = self.sheets.remove(id) {
self.sheet_order.retain(|i| i != id);
for subset in self.subsets.values_mut() {
subset.remove_sheet(id);
}
self.is_modified = true;
true
} else {
false
}
}
pub fn create_subset(&mut self, name: &str) -> &mut SheetSubset {
let subset = SheetSubset::with_name(name);
let subset_id = subset.id.clone();
self.subsets.insert(subset_id.clone(), subset);
self.subset_order.push(subset_id);
self.subsets.get_mut(&subset_id).unwrap()
}
pub fn add_subset(&mut self, subset: SheetSubset) {
self.subsets.insert(subset.id.clone(), subset);
self.subset_order.push(subset.id.clone());
self.is_modified = true;
}
pub fn get_subset(&self, id: &str) -> Option<&SheetSubset> {
self.subsets.get(id)
}
pub fn get_subset_mut(&mut self, id: &str) -> Option<&mut SheetSubset> {
self.subsets.get_mut(id)
}
pub fn add_sheet_to_subset(&mut self, sheet_id: &str, subset_id: &str) -> bool {
if let Some(sheet) = self.sheets.get_mut(sheet_id) {
if let Some(subset) = self.subsets.get_mut(subset_id) {
subset.add_sheet(sheet_id);
sheet.sheet_subset = Some(subset_id.to_string());
self.is_modified = true;
return true;
}
}
false
}
pub fn remove_sheet_from_subset(&mut self, sheet_id: &str, subset_id: &str) -> bool {
if let Some(subset) = self.subsets.get_mut(subset_id) {
let removed = subset.remove_sheet(sheet_id);
if removed {
if let Some(sheet) = self.sheets.get_mut(sheet_id) {
sheet.sheet_subset = None;
}
self.is_modified = true;
}
removed
} else {
false
}
}
pub fn move_sheet(&mut self, sheet_id: &str, new_index: usize) -> bool {
if let Some(current_index) = self.sheet_order.iter().position(|id| id == sheet_id) {
if new_index >= self.sheet_order.len() {
return false;
}
let sheet_id = self.sheet_order.remove(current_index);
self.sheet_order.insert(new_index, sheet_id);
self.is_modified = true;
true
} else {
false
}
}
pub fn reorder_sheets(&mut self, order: &[&str]) -> bool {
if order.len() != self.sheet_order.len() {
return false;
}
let new_order: Vec<String> = order.iter()
.map(|s| s.to_string())
.collect();
for id in &new_order {
if !self.sheets.contains_key(id) {
return false;
}
}
self.sheet_order = new_order;
self.is_modified = true;
true
}
pub fn duplicate_sheet(&mut self, source_id: &str, new_number: &str) -> Option<String> {
if let Some(source_sheet) = self.sheets.get(source_id) {
let mut new_sheet = source_sheet.clone();
new_sheet.properties.number = new_number.to_string();
new_sheet.properties.revision = "A".to_string();
new_sheet.properties.creation_date = SystemTime::now();
new_sheet.properties.modification_date = SystemTime::now();
new_sheet.id = uuid::Uuid::new_v4().to_string();
new_sheet.is_selected = false;
self.sheets.insert(new_sheet.id.clone(), new_sheet.clone());
self.sheet_order.push(new_sheet.id.clone());
self.is_modified = true;
Some(new_sheet.id)
} else {
None
}
}
pub fn add_resource_file(&mut self, resource_file: ResourceFile) {
self.resource_files.push(resource_file);
self.is_modified = true;
}
pub fn add_template(&mut self, template: SheetTemplate) {
self.templates.push(template);
self.is_modified = true;
}
pub fn add_callout_block(&mut self, callout: CalloutBlock) {
self.callout_blocks.push(callout);
self.is_modified = true;
}
pub fn add_revision_table(&mut self, table: RevisionTable) {
self.revision_tables.push(table);
self.is_modified = true;
}
pub fn sheet_count(&self) -> usize {
self.sheets.len()
}
pub fn subset_count(&self) -> usize {
self.subsets.len()
}
pub fn sheet_ids(&self) -> Vec<&str> {
self.sheet_order.iter().map(|s| s.as_str()).collect()
}
pub fn subset_ids(&self) -> Vec<&str> {
self.subset_order.iter().map(|s| s.as_str()).collect()
}
pub fn sheets_in_category(&self, category: &SheetCategory) -> Vec<&Sheet> {
self.sheets.values()
.filter(|s| &s.properties.category == category)
.collect()
}
pub fn sheets_by_status(&self, status: &SheetStatus) -> Vec<&Sheet> {
self.sheets.values()
.filter(|s| &s.properties.status == status)
.collect()
}
pub fn search_sheets(&self, query: &str) -> Vec<&Sheet> {
let query_lower = query.to_lowercase();
self.sheets.values()
.filter(|s| {
s.properties.title.to_lowercase().contains(&query_lower)
|| s.properties.number.to_lowercase().contains(&query_lower)
|| s.properties.keywords.iter().any(|k| k.to_lowercase().contains(&query_lower))
})
.collect()
}
pub fn get_statistics(&self) -> SheetSetStatistics {
let mut status_counts = HashMap::new();
let mut category_counts = HashMap::new();
for sheet in self.sheets.values() {
*status_counts.entry(sheet.properties.status.clone()).or_insert(0) += 1;
*category_counts.entry(sheet.properties.category.clone()).or_insert(0) += 1;
}
SheetSetStatistics {
total_sheets: self.sheets.len(),
total_subsets: self.subsets.len(),
status_counts,
category_counts,
resource_files: self.resource_files.len(),
templates: self.templates.len(),
callout_blocks: self.callout_blocks.len(),
revision_tables: self.revision_tables.len(),
is_modified: self.is_modified,
}
}
pub fn mark_as_modified(&mut self) {
self.is_modified = true;
self.properties.modification_date = SystemTime::now();
}
pub fn clear_modified(&mut self) {
self.is_modified = false;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SheetSetStatistics {
pub total_sheets: usize,
pub total_subsets: usize,
pub status_counts: HashMap<SheetStatus, usize>,
pub category_counts: HashMap<SheetCategory, usize>,
pub resource_files: usize,
pub templates: usize,
pub callout_blocks: usize,
pub revision_tables: usize,
pub is_modified: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceFile {
pub name: String,
pub file_path: PathBuf,
pub file_type: String,
pub description: String,
pub usage: String,
}
impl Default for ResourceFile {
fn default() -> Self {
Self::new()
}
}
impl ResourceFile {
pub fn new() -> Self {
Self {
name: String::new(),
file_path: PathBuf::new(),
file_type: String::new(),
description: String::new(),
usage: String::new(),
}
}
pub fn with_path(mut self, path: &str) -> Self {
self.file_path = PathBuf::from(path);
self.name = self.file_path.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| path.to_string());
self.file_type = self.file_path.extension()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| String::new);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SheetTemplate {
pub name: String,
pub template_file: PathBuf,
pub description: String,
pub layout_name: String,
pub is_default: bool,
}
impl Default for SheetTemplate {
fn default() -> Self {
Self::new()
}
}
impl SheetTemplate {
pub fn new() -> Self {
Self {
name: String::new(),
template_file: PathBuf::new(),
description: String::new(),
layout_name: String::new(),
is_default: false,
}
}
pub fn with_template(mut self, template: &str, layout: &str) -> Self {
self.template_file = PathBuf::from(template);
self.layout_name = layout.to_string();
self.name = self.template_file.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| template.to_string());
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CalloutBlock {
pub name: String,
pub block_name: String,
pub callout_type: CalloutType,
pub description: String,
pub is_system: bool,
}
impl Default for CalloutBlock {
fn default() -> Self {
Self::new()
}
}
impl CalloutBlock {
pub fn new() -> Self {
Self {
name: String::new(),
block_name: String::new(),
callout_type: CalloutType::Detail,
description: String::new(),
is_system: false,
}
}
pub fn with_block(mut self, name: &str, block: &str) -> Self {
self.name = name.to_string();
self.block_name = block.to_string();
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CalloutType {
Detail,
Section,
Elevation,
PlanView,
View,
}
impl Default for CalloutType {
fn default() -> Self {
CalloutType::Detail
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RevisionTable {
pub name: String,
pub description: String,
pub sheet_id: String,
pub revisions: Vec<RevisionEntry>,
pub position: (f64, f64),
pub width: f64,
pub height: f64,
}
impl Default for RevisionTable {
fn default() -> Self {
Self::new()
}
}
impl RevisionTable {
pub fn new() -> Self {
Self {
name: String::new(),
description: String::new(),
sheet_id: String::new(),
revisions: Vec::new(),
position: (0.0, 0.0),
width: 100.0,
height: 50.0,
}
}
pub fn add_revision(&mut self, revision: RevisionEntry) {
self.revisions.push(revision);
}
pub fn latest_revision(&self) -> Option<&RevisionEntry> {
self.revisions.last()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RevisionEntry {
pub number: String,
pub description: String,
pub date: SystemTime,
pub author: String,
pub checked_by: String,
}
impl Default for RevisionEntry {
fn default() -> Self {
Self::new()
}
}
impl RevisionEntry {
pub fn new() -> Self {
Self {
number: String::new(),
description: String::new(),
date: SystemTime::UNIX_EPOCH,
author: String::new(),
checked_by: String::new(),
}
}
pub fn with_description(mut self, desc: &str) -> Self {
self.description = desc.to_string();
self
}
}
#[derive(Debug, Clone)]
pub struct SheetSetManager {
sheet_sets: HashMap<String, SheetSet>,
active_sheet_set: Option<String>,
backup_enabled: bool,
backup_interval: u32,
auto_backup_count: u32,
}
impl Default for SheetSetManager {
fn default() -> Self {
Self::new()
}
}
impl SheetSetManager {
pub fn new() -> Self {
Self {
sheet_sets: HashMap::new(),
active_sheet_set: None,
backup_enabled: true,
backup_interval: 300,
auto_backup_count: 3,
}
}
pub fn create_sheet_set(&mut self, name: &str) -> &mut SheetSet {
let properties = SheetSetProperties::with_name(name);
let sheet_set = SheetSet::with_properties(properties);
self.sheet_sets.insert(name.to_string(), sheet_set);
self.active_sheet_set = Some(name.to_string());
self.sheet_sets.get_mut(name).unwrap()
}
pub fn add_sheet_set(&mut self, sheet_set: SheetSet) {
self.sheet_sets.insert(sheet_set.properties.name.clone(), sheet_set);
self.active_sheet_set = Some(sheet_set.properties.name.clone());
}
pub fn get_sheet_set(&self, name: &str) -> Option<&SheetSet> {
self.sheet_sets.get(name)
}
pub fn get_sheet_set_mut(&mut self, name: &str) -> Option<&mut SheetSet> {
self.sheet_sets.get_mut(name)
}
pub fn remove_sheet_set(&mut self, name: &str) -> bool {
self.sheet_sets.remove(name).is_some()
}
pub fn rename_sheet_set(&mut self, old_name: &str, new_name: &str) -> bool {
if let Some(mut sheet_set) = self.sheet_sets.remove(old_name) {
sheet_set.properties.name = new_name.to_string();
self.sheet_sets.insert(new_name.to_string(), sheet_set);
if self.active_sheet_set.as_ref() == Some(&old_name.to_string()) {
self.active_sheet_set = Some(new_name.to_string());
}
true
} else {
false
}
}
pub fn set_active_sheet_set(&mut self, name: Option<&str>) {
self.active_sheet_set = name.map(|s| s.to_string());
}
pub fn active_sheet_set(&self) -> Option<&SheetSet> {
self.active_sheet_set.as_ref().and_then(|name| self.sheet_sets.get(name))
}
pub fn active_sheet_set_mut(&mut self) -> Option<&mut SheetSet> {
self.active_sheet_set.as_ref().and_then(|name| self.sheet_sets.get_mut(name))
}
pub fn sheet_set_count(&self) -> usize {
self.sheet_sets.len()
}
pub fn sheet_set_names(&self) -> Vec<&str> {
self.sheet_sets.keys().map(|s| s.as_str()).collect()
}
pub fn duplicate_sheet_set(&mut self, source_name: &str, new_name: &str) -> bool {
if let Some(source_set) = self.sheet_sets.get(source_name) {
let mut new_set = source_set.clone();
new_set.properties.name = new_name.to_string();
new_set.properties.creation_date = SystemTime::now();
new_set.properties.modification_date = SystemTime::now();
new_set.is_modified = true;
self.sheet_sets.insert(new_name.to_string(), new_set);
true
} else {
false
}
}
pub fn export_sheet_list(&self, sheet_set_name: &str, format: &str) -> Option<String> {
if let Some(sheet_set) = self.sheet_sets.get(sheet_set_name) {
match format {
"csv" | "txt" => {
let mut output = String::new();
output.push_str("Number,Title,Revision,Category,Status,Designer\n");
for sheet in sheet_set.sheets.values() {
output.push_str(&format!(
"{},{},{},{},{},{}\n",
sheet.properties.number,
sheet.properties.title,
sheet.properties.revision,
sheet.properties.category.name(),
sheet.properties.status.name(),
sheet.properties.designer
));
}
Some(output)
}
"json" => {
let json = serde_json::to_string(&sheet_set.sheets).ok()?;
Some(json)
}
_ => None,
}
} else {
None
}
}
pub fn import_sheets(&mut self, sheet_set_name: &str, sheets: Vec<Sheet>) -> usize {
if let Some(sheet_set) = self.sheet_sets.get_mut(sheet_set_name) {
let mut imported = 0;
for sheet in sheets {
if sheet_set.sheets.get(&sheet.id).is_none() {
sheet_set.sheets.insert(sheet.id.clone(), sheet);
sheet_set.sheet_order.push(sheet.id);
imported += 1;
}
}
sheet_set.is_modified = true;
imported
} else {
0
}
}
pub fn set_backup_enabled(&mut self, enabled: bool) {
self.backup_enabled = enabled;
}
pub fn set_backup_interval(&mut self, interval: u32) {
self.backup_interval = interval;
}
pub fn clear(&mut self) {
self.sheet_sets.clear();
self.active_sheet_set = None;
}
pub fn get_statistics(&self) -> SheetSetManagerStatistics {
let total_sheets: usize = self.sheet_sets.values().map(|s| s.sheets.len()).sum();
let total_subsets: usize = self.sheet_sets.values().map(|s| s.subsets.len()).sum();
SheetSetManagerStatistics {
total_sheet_sets: self.sheet_sets.len(),
total_sheets,
total_subsets,
modified_sets: self.sheet_sets.values().filter(|s| s.is_modified).count(),
backup_enabled: self.backup_enabled,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SheetSetManagerStatistics {
pub total_sheet_sets: usize,
pub total_sheets: usize,
pub total_subsets: usize,
pub modified_sets: usize,
pub backup_enabled: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sheet_properties_creation() {
let props = SheetProperties::new()
.with_number("A-101")
.with_title("Floor Plan - Level 1");
assert_eq!(props.number, "A-101");
assert_eq!(props.title, "Floor Plan - Level 1");
assert_eq!(props.revision, "A");
}
#[test]
fn test_sheet_properties_revision() {
let mut props = SheetProperties::new();
props.increment_revision();
assert_eq!(props.revision, "B");
props.revision = "R1".to_string();
props.increment_revision();
assert_eq!(props.revision, "R2");
}
#[test]
fn test_sheet_creation() {
let mut sheet = Sheet::new();
sheet.properties = SheetProperties::new()
.with_number("A-101")
.with_title("Floor Plan");
assert_eq!(sheet.properties.number, "A-101");
assert!(!sheet.is_selected);
}
#[test]
fn test_sheet_subset() {
let mut subset = SheetSubset::with_name("Architectural");
subset.add_sheet("sheet1");
subset.add_sheet("sheet2");
assert_eq!(subset.sheet_count(), 2);
subset.remove_sheet("sheet1");
assert_eq!(subset.sheet_count(), 1);
}
#[test]
fn test_sheet_set_creation() {
let mut manager = SheetSetManager::new();
let sheet_set = manager.create_sheet_set("Project A");
sheet_set.properties.set_client_name("ABC Corporation");
assert_eq!(manager.sheet_set_count(), 1);
assert!(manager.get_sheet_set("Project A").is_some());
}
#[test]
fn test_sheet_set_add_sheet() {
let mut sheet_set = SheetSet::new();
let sheet = sheet_set.create_sheet("A-101", "Floor Plan");
assert_eq!(sheet_set.sheet_count(), 1);
assert!(sheet_set.find_sheet_by_number("A-101").is_some());
}
#[test]
fn test_sheet_set_add_to_subset() {
let mut sheet_set = SheetSet::new();
let sheet_id = sheet_set.create_sheet("A-101", "Floor Plan").id.clone();
let subset_id = sheet_set.create_subset("Drawings").id.clone();
assert!(sheet_set.add_sheet_to_subset(&sheet_id, &subset_id));
let subset = sheet_set.get_subset(&subset_id).unwrap();
assert_eq!(subset.sheet_count(), 1);
}
#[test]
fn test_sheet_set_move_sheet() {
let mut sheet_set = SheetSet::new();
sheet_set.create_sheet("A-101", "Sheet 1");
sheet_set.create_sheet("A-102", "Sheet 2");
sheet_set.create_sheet("A-103", "Sheet 3");
assert_eq!(sheet_set.sheet_order[0], "A-101");
sheet_set.move_sheet("A-101", 2);
assert_eq!(sheet_set.sheet_order[2], "A-101");
}
#[test]
fn test_sheet_set_duplicate_sheet() {
let mut sheet_set = SheetSet::new();
sheet_set.create_sheet("A-101", "Floor Plan");
let new_id = sheet_set.duplicate_sheet("A-101", "A-101-COPY").unwrap();
assert_eq!(sheet_set.sheet_count(), 2);
assert!(sheet_set.find_sheet_by_number("A-101-COPY").is_some());
}
#[test]
#[test]
fn test_sheet_search() {
let mut sheet_set = SheetSet::new();
sheet_set.create_sheet("A-101", "Floor Plan Level 1");
sheet_set.create_sheet("A-102", "Electrical Plan");
sheet_set.create_sheet("A-103", "HVAC Layout");
let results = sheet_set.search_sheets("floor");
assert_eq!(results.len(), 1);
}
#[test]
fn test_sheet_set_statistics() {
let mut sheet_set = SheetSet::new();
sheet_set.create_sheet("A-101", "Sheet 1");
sheet_set.create_sheet("A-102", "Sheet 2");
sheet_set.create_subset("Category");
let stats = sheet_set.get_statistics();
assert_eq!(stats.total_sheets, 2);
assert_eq!(stats.total_subsets, 1);
}
#[test]
fn test_sheet_category() {
assert_eq!(SheetCategory::Architectural.name(), "Architectural");
assert_eq!(SheetCategory::Mechanical.icon(), "⚙️");
}
#[test]
fn test_sheet_status() {
assert_eq!(SheetStatus::InProgress.name(), "In Progress");
let color = SheetStatus::InProgress.color();
assert_eq!(color, (255, 255, 0));
}
#[test]
fn test_sheet_properties_full_name() {
let props = SheetProperties::new()
.with_number("A-101")
.with_title("Floor Plan");
assert_eq!(props.full_name(), "A-101 - Floor Plan");
}
#[test]
fn test_resource_file() {
let resource = ResourceFile::new().with_path("C:/Resources/Title.dwg");
assert!(resource.name.contains("Title"));
assert_eq!(resource.file_type, "dwg");
}
#[test]
fn test_sheet_template() {
let template = SheetTemplate::new()
.with_template("templates/A3template.dwt", "A3 Layout");
assert!(template.name.contains("A3template"));
assert_eq!(template.layout_name, "A3 Layout");
}
#[test]
fn test_revision_table() {
let mut table = RevisionTable::new();
table.add_revision(
RevisionEntry::new()
.with_description("Initial issue")
);
assert_eq!(table.revisions.len(), 1);
}
#[test]
fn test_sheet_set_export() {
let mut manager = SheetSetManager::new();
manager.create_sheet_set("Project");
let csv = manager.export_sheet_list("Project", "csv");
assert!(csv.is_some());
assert!(csv.unwrap().contains("Number,Title"));
}
#[test]
fn test_sheet_set_import() {
let mut manager = SheetSetManager::new();
manager.create_sheet_set("Project");
let sheets = vec![Sheet::new()];
let imported = manager.import_sheets("Project", sheets);
assert_eq!(imported, 1);
}
#[test]
fn test_callout_block() {
let callout = CalloutBlock::new()
.with_block("Detail A", "DetailBlock");
assert_eq!(callout.block_name, "DetailBlock");
}
#[test]
fn test_sheet_annotation() {
let annotation = SheetAnnotation::new()
.with_content("Check this detail");
assert_eq!(annotation.content, "Check this detail");
}
#[test]
fn test_custom_data() {
let mut sheet = Sheet::new();
sheet.set_custom_data("ProjectPhase", "Design");
assert_eq!(sheet.get_custom_data("ProjectPhase"), Some("Design"));
}
#[test]
fn test_sheet_selection() {
let mut sheet = Sheet::new();
sheet.select();
assert!(sheet.is_selected);
sheet.deselect();
assert!(!sheet.is_selected);
}
#[test]
fn test_sheet_set_mark_modified() {
let mut sheet_set = SheetSet::new();
assert!(!sheet_set.is_modified);
sheet_set.mark_as_modified();
assert!(sheet_set.is_modified);
}
#[test]
fn test_sheet_set_reorder() {
let mut sheet_set = SheetSet::new();
sheet_set.create_sheet("A-101", "Sheet 1");
sheet_set.create_sheet("A-102", "Sheet 2");
let order = ["A-102", "A-101"];
assert!(sheet_set.reorder_sheets(&order));
assert_eq!(sheet_set.sheet_order[0], "A-102");
}
}