Skip to main content

acadrust/entities/
hatch.rs

1//! Hatch entity and boundary path types
2
3use crate::entities::{Entity, EntityCommon};
4use crate::types::{BoundingBox3D, Color, Handle, LineWeight, Transform, Transparency, Vector2, Vector3};
5
6/// Hatch pattern type
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum HatchPatternType {
10    /// User-defined pattern
11    UserDefined = 0,
12    /// Predefined pattern
13    Predefined = 1,
14    /// Custom pattern
15    Custom = 2,
16}
17
18/// Hatch style type
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub enum HatchStyleType {
22    /// Hatch "odd parity" area (normal)
23    Normal = 0,
24    /// Hatch outermost area only
25    Outer = 1,
26    /// Hatch through entire area
27    Ignore = 2,
28}
29
30/// Boundary path flags
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct BoundaryPathFlags {
34    bits: u32,
35}
36
37impl BoundaryPathFlags {
38    pub const DEFAULT: Self = Self { bits: 0 };
39    pub const EXTERNAL: Self = Self { bits: 1 };
40    pub const POLYLINE: Self = Self { bits: 2 };
41    pub const DERIVED: Self = Self { bits: 4 };
42    pub const TEXTBOX: Self = Self { bits: 8 };
43    pub const OUTERMOST: Self = Self { bits: 16 };
44    pub const NOT_CLOSED: Self = Self { bits: 32 };
45    pub const SELF_INTERSECTING: Self = Self { bits: 64 };
46    pub const TEXT_ISLAND: Self = Self { bits: 128 };
47    pub const DUPLICATE: Self = Self { bits: 256 };
48
49    pub fn new() -> Self {
50        Self::DEFAULT
51    }
52    
53    /// Create from raw bits
54    pub fn from_bits(bits: u32) -> Self {
55        Self { bits }
56    }
57    
58    /// Get raw bits
59    pub fn bits(&self) -> u32 {
60        self.bits
61    }
62
63    pub fn is_external(&self) -> bool {
64        self.bits & 1 != 0
65    }
66
67    pub fn is_polyline(&self) -> bool {
68        self.bits & 2 != 0
69    }
70
71    pub fn is_derived(&self) -> bool {
72        self.bits & 4 != 0
73    }
74    
75    pub fn is_outermost(&self) -> bool {
76        self.bits & 16 != 0
77    }
78    
79    pub fn is_not_closed(&self) -> bool {
80        self.bits & 32 != 0
81    }
82
83    pub fn set_external(&mut self, value: bool) {
84        if value {
85            self.bits |= 1;
86        } else {
87            self.bits &= !1;
88        }
89    }
90
91    pub fn set_polyline(&mut self, value: bool) {
92        if value {
93            self.bits |= 2;
94        } else {
95            self.bits &= !2;
96        }
97    }
98    
99    pub fn set_derived(&mut self, value: bool) {
100        if value {
101            self.bits |= 4;
102        } else {
103            self.bits &= !4;
104        }
105    }
106}
107
108impl Default for BoundaryPathFlags {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114/// Edge type for boundary paths
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117pub enum EdgeType {
118    Polyline = 0,
119    Line = 1,
120    CircularArc = 2,
121    EllipticArc = 3,
122    Spline = 4,
123}
124
125/// Line edge in a boundary path
126#[derive(Debug, Clone, PartialEq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct LineEdge {
129    /// Start point (in OCS)
130    pub start: Vector2,
131    /// End point (in OCS)
132    pub end: Vector2,
133}
134
135/// Circular arc edge in a boundary path
136#[derive(Debug, Clone, PartialEq)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138pub struct CircularArcEdge {
139    /// Center point (in OCS)
140    pub center: Vector2,
141    /// Radius
142    pub radius: f64,
143    /// Start angle in radians
144    pub start_angle: f64,
145    /// End angle in radians
146    pub end_angle: f64,
147    /// Counter-clockwise flag
148    pub counter_clockwise: bool,
149}
150
151/// Elliptic arc edge in a boundary path
152#[derive(Debug, Clone, PartialEq)]
153#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
154pub struct EllipticArcEdge {
155    /// Center point (in OCS)
156    pub center: Vector2,
157    /// Endpoint of major axis relative to center (in OCS)
158    pub major_axis_endpoint: Vector2,
159    /// Ratio of minor axis to major axis
160    pub minor_axis_ratio: f64,
161    /// Start angle in radians
162    pub start_angle: f64,
163    /// End angle in radians
164    pub end_angle: f64,
165    /// Counter-clockwise flag
166    pub counter_clockwise: bool,
167}
168
169/// Spline edge in a boundary path
170#[derive(Debug, Clone, PartialEq)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
172pub struct SplineEdge {
173    /// Degree of the spline
174    pub degree: i32,
175    /// Rational flag
176    pub rational: bool,
177    /// Periodic flag
178    pub periodic: bool,
179    /// Knot values
180    pub knots: Vec<f64>,
181    /// Control points (X, Y, weight)
182    pub control_points: Vec<Vector3>,
183    /// Fit points
184    pub fit_points: Vec<Vector2>,
185    /// Start tangent
186    pub start_tangent: Vector2,
187    /// End tangent
188    pub end_tangent: Vector2,
189}
190
191/// Polyline edge in a boundary path
192#[derive(Debug, Clone, PartialEq)]
193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
194pub struct PolylineEdge {
195    /// Vertices (X, Y, bulge)
196    pub vertices: Vec<Vector3>,
197    /// Is closed flag
198    pub is_closed: bool,
199}
200
201impl PolylineEdge {
202    /// Create a new polyline edge
203    pub fn new(vertices: Vec<Vector2>, is_closed: bool) -> Self {
204        Self {
205            vertices: vertices.into_iter().map(|v| Vector3::new(v.x, v.y, 0.0)).collect(),
206            is_closed,
207        }
208    }
209
210    /// Add a vertex with bulge
211    pub fn add_vertex(&mut self, point: Vector2, bulge: f64) {
212        self.vertices.push(Vector3::new(point.x, point.y, bulge));
213    }
214
215    /// Check if the polyline has any bulges
216    pub fn has_bulge(&self) -> bool {
217        self.vertices.iter().any(|v| v.z.abs() > 1e-10)
218    }
219}
220
221/// Boundary path edge
222#[derive(Debug, Clone, PartialEq)]
223#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
224pub enum BoundaryEdge {
225    Line(LineEdge),
226    CircularArc(CircularArcEdge),
227    EllipticArc(EllipticArcEdge),
228    Spline(SplineEdge),
229    Polyline(PolylineEdge),
230}
231
232/// Boundary path for a hatch
233#[derive(Debug, Clone, PartialEq)]
234#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
235pub struct BoundaryPath {
236    /// Boundary path flags
237    pub flags: BoundaryPathFlags,
238    /// Edges that form the boundary
239    pub edges: Vec<BoundaryEdge>,
240    /// Handles of associated boundary objects (for associative hatches)
241    pub boundary_handles: Vec<Handle>,
242}
243
244impl BoundaryPath {
245    /// Create a new boundary path
246    pub fn new() -> Self {
247        Self {
248            flags: BoundaryPathFlags::new(),
249            edges: Vec::new(),
250            boundary_handles: Vec::new(),
251        }
252    }
253    
254    /// Create a boundary path with flags
255    pub fn with_flags(flags: BoundaryPathFlags) -> Self {
256        Self {
257            flags,
258            edges: Vec::new(),
259            boundary_handles: Vec::new(),
260        }
261    }
262
263    /// Create an external boundary path
264    pub fn external() -> Self {
265        let mut path = Self::new();
266        path.flags.set_external(true);
267        path
268    }
269
270    /// Add an edge to the boundary
271    pub fn add_edge(&mut self, edge: BoundaryEdge) {
272        // Update polyline flag if needed
273        if matches!(edge, BoundaryEdge::Polyline(_)) {
274            self.flags.set_polyline(true);
275        }
276        self.edges.push(edge);
277    }
278    
279    /// Add a boundary object handle
280    pub fn add_boundary_handle(&mut self, handle: Handle) {
281        self.boundary_handles.push(handle);
282    }
283
284    /// Check if this is a polyline boundary
285    pub fn is_polyline(&self) -> bool {
286        self.edges.len() == 1 && matches!(self.edges[0], BoundaryEdge::Polyline(_))
287    }
288}
289
290impl Default for BoundaryPath {
291    fn default() -> Self {
292        Self::new()
293    }
294}
295
296/// Hatch pattern line
297#[derive(Debug, Clone, PartialEq)]
298#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
299pub struct HatchPatternLine {
300    /// Pattern line angle in radians
301    pub angle: f64,
302    /// Pattern line base point
303    pub base_point: Vector2,
304    /// Pattern line offset
305    pub offset: Vector2,
306    /// Dash lengths (positive = dash, negative = space)
307    pub dash_lengths: Vec<f64>,
308}
309
310/// Hatch pattern
311#[derive(Debug, Clone, PartialEq)]
312#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
313pub struct HatchPattern {
314    /// Pattern name
315    pub name: String,
316    /// Pattern description
317    pub description: String,
318    /// Pattern lines
319    pub lines: Vec<HatchPatternLine>,
320}
321
322impl HatchPattern {
323    /// Create a new pattern
324    pub fn new(name: impl Into<String>) -> Self {
325        Self {
326            name: name.into(),
327            description: String::new(),
328            lines: Vec::new(),
329        }
330    }
331
332    /// Create a solid fill pattern
333    pub fn solid() -> Self {
334        Self::new("SOLID")
335    }
336
337    /// Add a pattern line
338    pub fn add_line(&mut self, line: HatchPatternLine) {
339        self.lines.push(line);
340    }
341
342    /// Update pattern with scale and rotation
343    pub fn update(&mut self, _base_point: Vector2, angle: f64, scale: f64) {
344        for line in &mut self.lines {
345            line.angle += angle;
346            line.offset = line.offset * scale;
347            for dash in &mut line.dash_lengths {
348                *dash *= scale;
349            }
350        }
351    }
352}
353
354/// Gradient color with value
355#[derive(Debug, Clone, PartialEq)]
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357pub struct GradientColorEntry {
358    /// Gradient value (position 0.0 - 1.0)
359    pub value: f64,
360    /// Color at this position
361    pub color: Color,
362}
363
364/// Gradient color pattern
365#[derive(Debug, Clone, PartialEq)]
366#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
367pub struct HatchGradientPattern {
368    /// Gradient is enabled
369    pub enabled: bool,
370    /// Reserved value (DXF 451)
371    pub reserved: i32,
372    /// Gradient angle in radians
373    pub angle: f64,
374    /// Gradient shift (0.0 - 1.0)
375    pub shift: f64,
376    /// Single color gradient flag
377    pub is_single_color: bool,
378    /// Color tint (for single color gradients)
379    pub color_tint: f64,
380    /// Gradient colors with their values
381    pub colors: Vec<GradientColorEntry>,
382    /// Gradient name (e.g., "LINEAR", "CYLINDER", etc.)
383    pub name: String,
384}
385
386impl HatchGradientPattern {
387    pub fn new() -> Self {
388        Self {
389            enabled: false,
390            reserved: 0,
391            angle: 0.0,
392            shift: 0.0,
393            is_single_color: false,
394            color_tint: 0.0,
395            colors: Vec::new(),
396            name: String::new(),
397        }
398    }
399    
400    /// Check if gradient is enabled
401    pub fn is_enabled(&self) -> bool {
402        self.enabled
403    }
404    
405    /// Add a color to the gradient
406    pub fn add_color(&mut self, value: f64, color: Color) {
407        self.colors.push(GradientColorEntry { value, color });
408    }
409}
410
411impl Default for HatchGradientPattern {
412    fn default() -> Self {
413        Self::new()
414    }
415}
416
417/// Hatch entity
418///
419/// Represents a filled or patterned area defined by boundary paths.
420#[derive(Debug, Clone, PartialEq)]
421#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
422pub struct Hatch {
423    pub common: EntityCommon,
424    /// Elevation of the hatch
425    pub elevation: f64,
426    /// Normal vector (extrusion direction)
427    pub normal: Vector3,
428    /// Hatch pattern
429    pub pattern: HatchPattern,
430    /// Is solid fill
431    pub is_solid: bool,
432    /// Is associative (linked to boundary objects)
433    pub is_associative: bool,
434    /// Hatch pattern type
435    pub pattern_type: HatchPatternType,
436    /// Hatch pattern angle in radians
437    pub pattern_angle: f64,
438    /// Hatch pattern scale
439    pub pattern_scale: f64,
440    /// Is pattern double (for pattern fill only)
441    pub is_double: bool,
442    /// Hatch style
443    pub style: HatchStyleType,
444    /// Boundary paths (loops)
445    pub paths: Vec<BoundaryPath>,
446    /// Seed points (in OCS)
447    pub seed_points: Vec<Vector2>,
448    /// Pixel size for intersection operations
449    pub pixel_size: f64,
450    /// Gradient color pattern
451    pub gradient_color: HatchGradientPattern,
452}
453
454impl Hatch {
455    /// Create a new hatch entity
456    pub fn new() -> Self {
457        Self {
458            common: EntityCommon::default(),
459            elevation: 0.0,
460            normal: Vector3::new(0.0, 0.0, 1.0),
461            pattern: HatchPattern::solid(),
462            is_solid: true,
463            is_associative: false,
464            pattern_type: HatchPatternType::Predefined,
465            pattern_angle: 0.0,
466            pattern_scale: 1.0,
467            is_double: false,
468            style: HatchStyleType::Normal,
469            paths: Vec::new(),
470            seed_points: Vec::new(),
471            pixel_size: 0.0,
472            gradient_color: HatchGradientPattern::new(),
473        }
474    }
475
476    /// Create a solid fill hatch
477    pub fn solid() -> Self {
478        let mut hatch = Self::new();
479        hatch.is_solid = true;
480        hatch.pattern = HatchPattern::solid();
481        hatch
482    }
483
484    /// Create a pattern fill hatch
485    pub fn with_pattern(pattern: HatchPattern) -> Self {
486        let mut hatch = Self::new();
487        hatch.is_solid = false;
488        hatch.pattern = pattern;
489        hatch
490    }
491
492    /// Builder: Set the pattern angle
493    pub fn with_pattern_angle(mut self, angle: f64) -> Self {
494        self.pattern_angle = angle;
495        self.pattern.update(Vector2::new(0.0, 0.0), angle, self.pattern_scale);
496        self
497    }
498
499    /// Builder: Set the pattern scale
500    pub fn with_pattern_scale(mut self, scale: f64) -> Self {
501        self.pattern_scale = scale;
502        self.pattern.update(Vector2::new(0.0, 0.0), self.pattern_angle, scale);
503        self
504    }
505
506    /// Builder: Set the normal vector
507    pub fn with_normal(mut self, normal: Vector3) -> Self {
508        self.normal = normal;
509        self
510    }
511
512    /// Builder: Set the elevation
513    pub fn with_elevation(mut self, elevation: f64) -> Self {
514        self.elevation = elevation;
515        self
516    }
517
518    /// Add a boundary path
519    pub fn add_path(&mut self, path: BoundaryPath) {
520        self.paths.push(path);
521    }
522
523    /// Add a seed point
524    pub fn add_seed_point(&mut self, point: Vector2) {
525        self.seed_points.push(point);
526    }
527
528    /// Set the pattern angle (updates pattern)
529    pub fn set_pattern_angle(&mut self, angle: f64) {
530        self.pattern_angle = angle;
531        self.pattern.update(Vector2::new(0.0, 0.0), angle, self.pattern_scale);
532    }
533
534    /// Set the pattern scale (updates pattern)
535    pub fn set_pattern_scale(&mut self, scale: f64) {
536        self.pattern_scale = scale;
537        self.pattern.update(Vector2::new(0.0, 0.0), self.pattern_angle, scale);
538    }
539
540    /// Get the number of boundary paths
541    pub fn path_count(&self) -> usize {
542        self.paths.len()
543    }
544
545    /// Check if the hatch has any paths
546    pub fn has_paths(&self) -> bool {
547        !self.paths.is_empty()
548    }
549}
550
551impl Default for Hatch {
552    fn default() -> Self {
553        Self::new()
554    }
555}
556
557impl Entity for Hatch {
558    fn handle(&self) -> Handle {
559        self.common.handle
560    }
561
562    fn set_handle(&mut self, handle: Handle) {
563        self.common.handle = handle;
564    }
565
566    fn layer(&self) -> &str {
567        &self.common.layer
568    }
569
570    fn set_layer(&mut self, layer: String) {
571        self.common.layer = layer;
572    }
573
574    fn color(&self) -> Color {
575        self.common.color
576    }
577
578    fn set_color(&mut self, color: Color) {
579        self.common.color = color;
580    }
581
582    fn line_weight(&self) -> LineWeight {
583        self.common.line_weight
584    }
585
586    fn set_line_weight(&mut self, weight: LineWeight) {
587        self.common.line_weight = weight;
588    }
589
590    fn transparency(&self) -> Transparency {
591        self.common.transparency
592    }
593
594    fn set_transparency(&mut self, transparency: Transparency) {
595        self.common.transparency = transparency;
596    }
597
598    fn is_invisible(&self) -> bool {
599        self.common.invisible
600    }
601
602    fn set_invisible(&mut self, invisible: bool) {
603        self.common.invisible = invisible;
604    }
605
606    fn bounding_box(&self) -> BoundingBox3D {
607        // Calculate bounding box from all boundary paths
608        let mut all_points = Vec::new();
609
610        for path in &self.paths {
611            for edge in &path.edges {
612                match edge {
613                    BoundaryEdge::Line(line) => {
614                        all_points.push(Vector3::new(line.start.x, line.start.y, self.elevation));
615                        all_points.push(Vector3::new(line.end.x, line.end.y, self.elevation));
616                    }
617                    BoundaryEdge::CircularArc(arc) => {
618                        // Add center and radius-based bounds
619                        all_points.push(Vector3::new(arc.center.x - arc.radius, arc.center.y - arc.radius, self.elevation));
620                        all_points.push(Vector3::new(arc.center.x + arc.radius, arc.center.y + arc.radius, self.elevation));
621                    }
622                    BoundaryEdge::EllipticArc(ellipse) => {
623                        // Add center and major axis-based bounds
624                        let major_len = ellipse.major_axis_endpoint.length();
625                        all_points.push(Vector3::new(ellipse.center.x - major_len, ellipse.center.y - major_len, self.elevation));
626                        all_points.push(Vector3::new(ellipse.center.x + major_len, ellipse.center.y + major_len, self.elevation));
627                    }
628                    BoundaryEdge::Spline(spline) => {
629                        for cp in &spline.control_points {
630                            all_points.push(Vector3::new(cp.x, cp.y, self.elevation));
631                        }
632                    }
633                    BoundaryEdge::Polyline(poly) => {
634                        for v in &poly.vertices {
635                            all_points.push(Vector3::new(v.x, v.y, self.elevation));
636                        }
637                    }
638                }
639            }
640        }
641
642        if all_points.is_empty() {
643            BoundingBox3D::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0))
644        } else {
645            BoundingBox3D::from_points(&all_points).unwrap_or_else(|| BoundingBox3D::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0)))
646        }
647    }
648
649    fn translate(&mut self, offset: Vector3) {
650        super::translate::translate_hatch(self, offset);
651    }
652
653    fn entity_type(&self) -> &'static str {
654        "HATCH"
655    }
656    
657    fn apply_transform(&mut self, transform: &Transform) {
658        super::transform::transform_hatch(self, transform);
659    }
660    
661    fn apply_mirror(&mut self, transform: &crate::types::Transform) {
662        super::mirror::mirror_hatch(self, transform);
663    }
664}