• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DomCR / ACadSharp / 24027584052

06 Apr 2026 09:59AM UTC coverage: 76.825% (-0.02%) from 76.841%
24027584052

Pull #1022

github

web-flow
Merge 7ed613d2f into bc22ae5fa
Pull Request #1022: Issue-998 Improve Insert.GetBoundingBox for rotation and scaling

8412 of 11888 branches covered (70.76%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

14 existing lines in 2 files now uncovered.

30194 of 38364 relevant lines covered (78.7%)

150817.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.63
/src/ACadSharp/Entities/Insert.cs
1
using ACadSharp.Attributes;
2
using ACadSharp.Extensions;
3
using ACadSharp.Objects;
4
using ACadSharp.Tables;
5
using CSMath;
6
using System;
7
using System.Collections.Generic;
8
using System.Linq;
9

10
namespace ACadSharp.Entities;
11

12
/// <summary>
13
/// Represents a <see cref="Insert"/> entity.
14
/// </summary>
15
/// <remarks>
16
/// Object name <see cref="DxfFileToken.EntityInsert"/> <br/>
17
/// Dxf class name <see cref="DxfSubclassMarker.Insert"/>
18
/// </remarks>
19
[DxfName(DxfFileToken.EntityInsert)]
20
[DxfSubClass(DxfSubclassMarker.Insert)]
21
public class Insert : Entity
22
{
23
        /// <summary>
24
        /// Attributes from the block reference
25
        /// </summary>
26
        /// <remarks>
27
        /// If an attribute should be added in this collection a definition will be added into the block reference as well
28
        /// </remarks>
29
        public SeqendCollection<AttributeEntity> Attributes { get; private set; }
27,661✔
30

31
        /// <summary>
32
        /// Gets the insert block definition.
33
        /// </summary>
34
        [DxfCodeValue(DxfReferenceType.Name, 2)]
35
        public BlockRecord Block { get; internal set; }
18,769✔
36

37
        /// <summary>
38
        /// Column count
39
        /// </summary>
40
        [DxfCodeValue(DxfReferenceType.Optional, 70)]
41
        public ushort ColumnCount { get; set; } = 1;
8,953✔
42

43
        /// <summary>
44
        /// Column spacing
45
        /// </summary>
46
        [DxfCodeValue(DxfReferenceType.Optional, 44)]
47
        public double ColumnSpacing { get; set; } = 0;
8,526✔
48

49
        /// <summary>
50
        /// True if the insert has attribute entities in it
51
        /// </summary>
52
        [DxfCodeValue(DxfReferenceType.Ignored, 66)]
53
        public bool HasAttributes
54
        { get { return this.Attributes.Any(); } }
1,323✔
55

56
        /// <inheritdoc/>
57
        public override bool HasDynamicSubclass => true;
2✔
58

59
        /// <summary>
60
        /// A 3D WCS coordinate representing the insertion or origin point.
61
        /// </summary>
62
        [DxfCodeValue(10, 20, 30)]
63
        public XYZ InsertPoint { get; set; } = XYZ.Zero;
47,672✔
64

65
        /// <summary>
66
        /// Specifies the rotation angle for the object.
67
        /// </summary>
68
        public bool IsMultiple { get { return this.RowCount > 1 || this.ColumnCount > 1; } }
1,095✔
69

70
        /// <summary>
71
        /// Specifies the three-dimensional normal unit vector for the object.
72
        /// </summary>
73
        [DxfCodeValue(210, 220, 230)]
74
        public XYZ Normal { get; set; } = XYZ.AxisZ;
12,090✔
75

76
        /// <inheritdoc/>
77
        public override string ObjectName => DxfFileToken.EntityInsert;
18,401✔
78

79
        /// <inheritdoc/>
80
        public override ObjectType ObjectType
81
        {
82
                get
83
                {
86✔
84
                        if (this.RowCount > 1 || this.ColumnCount > 1)
86✔
85
                        {
16✔
86
                                return ObjectType.MINSERT;
16✔
87
                        }
88
                        else
89
                        {
70✔
90
                                return ObjectType.INSERT;
70✔
91
                        }
92
                }
86✔
93
        }
94

95
        /// <summary>
96
        /// Specifies the rotation angle for the object.
97
        /// </summary>
98
        /// <value>
99
        /// The rotation angle in radians.
100
        /// </value>
101
        [DxfCodeValue(DxfReferenceType.IsAngle, 50)]
102
        public double Rotation { get; set; } = 0.0;
12,768✔
103

104
        /// <summary>
105
        /// Row count
106
        /// </summary>
107
        [DxfCodeValue(DxfReferenceType.Optional, 71)]
108
        public ushort RowCount { get; set; } = 1;
8,977✔
109

110
        /// <summary>
111
        /// Row spacing
112
        /// </summary>
113
        [DxfCodeValue(DxfReferenceType.Optional, 45)]
114
        public double RowSpacing { get; set; } = 0;
8,526✔
115

116
        /// <summary>
117
        /// Gets or set the spatial filter entry for this <see cref="Insert"/> entity.
118
        /// </summary>
119
        public SpatialFilter SpatialFilter
120
        {
121
                get
122
                {
×
123
                        if (this.XDictionary != null
×
124
                                && this.XDictionary.TryGetEntry(Filter.FilterEntryName, out CadDictionary filters))
×
125
                        {
×
126
                                return filters.GetEntry<SpatialFilter>(SpatialFilter.SpatialFilterEntryName);
×
127
                        }
128

129
                        return null;
×
130
                }
×
131
                set
132
                {
11✔
133
                        if (this.XDictionary == null)
11✔
134
                        {
11✔
135
                                this.CreateExtendedDictionary();
11✔
136
                        }
11✔
137

138
                        if (!this.XDictionary.TryGetEntry(Filter.FilterEntryName, out CadDictionary filters))
11✔
139
                        {
11✔
140
                                filters = new CadDictionary(Filter.FilterEntryName);
11✔
141
                                this.XDictionary.Add(filters);
11✔
142
                        }
11✔
143

144
                        filters.Remove(SpatialFilter.SpatialFilterEntryName);
11✔
145
                        filters.Add(SpatialFilter.SpatialFilterEntryName, value);
11✔
146
                }
11✔
147
        }
148

149
        /// <inheritdoc/>
150
        public override string SubclassMarker => this.IsMultiple ? DxfSubclassMarker.MInsert : DxfSubclassMarker.Insert;
322!
151

152
        /// <summary>
153
        /// X scale factor.
154
        /// </summary>
155
        [DxfCodeValue(41)]
156
        public double XScale
157
        {
158
                get
159
                {
1,830✔
160
                        return this._xscale;
1,830✔
161
                }
1,830✔
162
                set
163
                {
4,937✔
164
                        if (value.Equals(0))
4,937✔
165
                        {
1✔
166
                                string name = nameof(this.XScale);
1✔
167
                                throw new ArgumentOutOfRangeException(name, value, $"{name} value must be none zero.");
1✔
168
                        }
169
                        this._xscale = value;
4,936✔
170
                }
4,936✔
171
        }
172

173
        /// <summary>
174
        /// Y scale factor.
175
        /// </summary>
176
        [DxfCodeValue(42)]
177
        public double YScale
178
        {
179
                get
180
                {
1,769✔
181
                        return this._yscale;
1,769✔
182
                }
1,769✔
183
                set
184
                {
4,732✔
185
                        if (value.Equals(0))
4,732✔
186
                        {
1✔
187
                                string name = nameof(this.YScale);
1✔
188
                                throw new ArgumentOutOfRangeException(name, value, $"{name} value must be none zero.");
1✔
189
                        }
190
                        this._yscale = value;
4,731✔
191
                }
4,731✔
192
        }
193

194
        /// <summary>
195
        /// Z scale factor.
196
        /// </summary>
197
        [DxfCodeValue(43)]
198
        public double ZScale
199
        {
200
                get
201
                {
1,763✔
202
                        return this._zscale;
1,763✔
203
                }
1,763✔
204
                set
205
                {
4,721✔
206
                        if (value.Equals(0))
4,721✔
207
                        {
1✔
208
                                string name = nameof(this.ZScale);
1✔
209
                                throw new ArgumentOutOfRangeException(name, value, $"{name} value must be none zero.");
1✔
210
                        }
211
                        this._zscale = value;
4,720✔
212
                }
4,720✔
213
        }
214

215
        private double _xscale = 1;
8,174✔
216

217
        private double _yscale = 1;
8,174✔
218

219
        private double _zscale = 1;
8,174✔
220

221
        /// <summary>
222
        /// Constructor to reference an insert to a block record
223
        /// </summary>
224
        /// <param name="block">Block Record to reference</param>
225
        /// <exception cref="ArgumentNullException"></exception>
226
        public Insert(BlockRecord block) : this()
98✔
227
        {
98✔
228
                if (block is null) throw new ArgumentNullException(nameof(block));
98!
229

230
                if (block.Document != null)
98✔
231
                {
46✔
232
                        this.Block = (BlockRecord)block.Clone();
46✔
233
                }
46✔
234
                else
235
                {
52✔
236
                        this.Block = block;
52✔
237
                }
52✔
238

239
                foreach (var item in block.AttributeDefinitions)
344✔
240
                {
25✔
241
                        this.Attributes.Add(new AttributeEntity(item));
25✔
242
                }
25✔
243
        }
98✔
244

245
        internal Insert() : base()
8,174✔
246
        {
8,174✔
247
                this.initCollections();
8,174✔
248
        }
8,174✔
249

250
        /// <inheritdoc/>
251
        public override void ApplyTransform(Transform transform)
252
        {
×
253
                XYZ newPosition = transform.ApplyTransform(this.InsertPoint);
×
254
                XYZ newNormal = this.transformNormal(transform, this.Normal);
×
255

256
                Matrix3 transOW = Matrix3.ArbitraryAxis(this.Normal);
×
257
                transOW *= Matrix3.RotationZ(this.Rotation);
×
258

259
                Matrix3 transWO = Matrix3.ArbitraryAxis(newNormal);
×
260
                transWO = transWO.Transpose();
×
261

262
                var transformation = new Matrix3(transform.Matrix);
×
263
                XYZ v = transOW * XYZ.AxisX;
×
264
                v = transformation * v;
×
265
                v = transWO * v;
×
266
                double newRotation = new XY(v.X, v.Y).GetAngle();
×
267

268
                transWO = Matrix3.RotationZ(newRotation).Transpose() * transWO;
×
269

270
                XYZ s = transOW * new XYZ(this.XScale, this.YScale, this.ZScale);
×
271
                s = transformation * s;
×
272
                s = transWO * s;
×
273
                XYZ newScale = new XYZ(
×
274
                        MathHelper.IsZero(s.X) ? MathHelper.Epsilon : s.X,
×
275
                        MathHelper.IsZero(s.Y) ? MathHelper.Epsilon : s.Y,
×
276
                        MathHelper.IsZero(s.Z) ? MathHelper.Epsilon : s.Z);
×
277

278
                this.Normal = newNormal;
×
279
                this.InsertPoint = newPosition;
×
280
                this.XScale = newScale.X;
×
281
                this.YScale = newScale.Y;
×
282
                this.ZScale = newScale.Z;
×
283
                this.Rotation = newRotation;
×
284

285
                foreach (AttributeEntity att in this.Attributes)
×
286
                {
×
287
                        att.ApplyTransform(transform);
×
288
                }
×
289
        }
×
290

291
        /// <inheritdoc/>
292
        public override CadObject Clone()
293
        {
181✔
294
                Insert clone = (Insert)base.Clone();
181✔
295

296
                clone.Block = (BlockRecord)this.Block?.Clone();
181✔
297

298
                clone.initCollections();
181✔
299
                foreach (var att in this.Attributes)
545✔
300
                {
1✔
301
                        clone.Attributes.Add((AttributeEntity)att.Clone());
1✔
302
                }
1✔
303

304
                return clone;
181✔
305
        }
181✔
306

307
        /// <summary>
308
        /// Returns an enumerable collection of entities representing the exploded contents of the block, with all entities
309
        /// transformed into the current coordinate system.
310
        /// </summary>
311
        /// <remarks>The returned entities are clones or converted equivalents of the original block entities, with
312
        /// geometric transformations applied. For example, arcs and circles are converted to their corresponding geometric
313
        /// representations. The original block and its entities remain unchanged.</remarks>
314
        /// <returns>An enumerable collection of <see cref="Entity"/> objects that make up the exploded block. Each entity is
315
        /// transformed according to the block's transform. The collection may be empty if the block contains no entities.</returns>
316
        public IEnumerable<Entity> Explode()
317
        {
×
318
                Transform transform = this.GetTransform();
×
319
                foreach (var e in this.Block.Entities)
×
320
                {
×
321
                        Entity c;
322
                        switch (e)
×
323
                        {
324
                                case Arc arc:
325
                                        arc.GetEndVertices(out XYZ start, out XYZ end);
×
326

327
                                        Arc a = new Arc(
×
328
                                                        transform.ApplyTransform(arc.Center),
×
329
                                                        transform.ApplyTransform(start),
×
330
                                                        transform.ApplyTransform(end),
×
331
                                                        arc.Normal);
×
332

333
                                        a.MatchProperties(e);
×
334

335
                                        yield return a;
×
336
                                        continue;
×
337
                                case Circle circle:
338
                                        c = new Ellipse()
×
339
                                        {
×
340
                                                MajorAxisEndPoint = XYZ.AxisX * circle.Radius,
×
341
                                                RadiusRatio = 1,
×
342
                                                Center = circle.Center,
×
343
                                                Normal = circle.Normal,
×
344
                                        };
×
345
                                        c.MatchProperties(e);
×
346
                                        break;
×
347
                                default:
348
                                        c = e.CloneTyped();
×
349
                                        break;
×
350
                        }
351

352
                        c.ApplyTransform(transform);
×
353

354
                        yield return c;
×
355
                }
×
356
        }
×
357

358
        /// <inheritdoc/>
359
        public override BoundingBox GetBoundingBox()
360
        {
5✔
361
                BoundingBox box = this.Block.GetBoundingBox();
5✔
362

363
                var scale = new XYZ(this.XScale, this.YScale, this.ZScale);
5✔
364
                var min = box.Min * scale + this.InsertPoint;
5✔
365
                var max = box.Max * scale + this.InsertPoint;
5✔
366

367
                if (this.Rotation != 0)
5✔
368
                {
1✔
369
                        var t = Transform.CreateRotation(this.Normal, this.Rotation);
1✔
370
                        min = t.ApplyRotation(min);
1✔
371
                        max = t.ApplyRotation(max);
1✔
372
                }
1✔
373

374
                return new BoundingBox(min, max);
5✔
375
        }
5✔
376

377
        /// <summary>
378
        /// Get the transform that will be applied to the entities in the <see cref="BlockRecord"/> when this entity is processed.
379
        /// </summary>
380
        /// <returns></returns>
381
        public Transform GetTransform()
382
        {
1,388✔
383
                var world = Matrix4.GetArbitraryAxis(this.Normal);
1,388✔
384
                var translation = Transform.CreateTranslation(this.InsertPoint);
1,388✔
385
                var rotation = Transform.CreateRotation(XYZ.AxisZ, this.Rotation);
1,388✔
386
                var scale = Transform.CreateScaling(new XYZ(this.XScale, this.YScale, this.ZScale));
1,388✔
387

388
                return new Transform(world * translation.Matrix * rotation.Matrix * scale.Matrix);
1,388✔
389
        }
1,388✔
390

391
        /// <summary>
392
        /// Updates all attribute definitions contained in the block reference as <see cref="AttributeDefinition"/> entities in the insert.
393
        /// </summary>
394
        /// <remarks>
395
        /// This will update the attributes based on their <see cref="AttributeBase.Tag"/>.
396
        /// </remarks>
397
        public void UpdateAttributes()
398
        {
×
399
                var atts = this.Attributes.ToArray();
×
400

401
                foreach (AttributeEntity att in atts)
×
402
                {
×
403
                        //Tags are not unique, is it needed? check how the different applications link the atts
404
                        if (!this.Block.AttributeDefinitions.Select(d => d.Tag).Contains(att.Tag))
×
405
                        {
×
406
                                this.Attributes.Remove(att);
×
407
                        }
×
408
                }
×
409

410
                foreach (AttributeDefinition attdef in this.Block.AttributeDefinitions)
×
411
                {
×
412
                        if (!this.Attributes.Select(d => d.Tag).Contains(attdef.Tag))
×
413
                        {
×
UNCOV
414
                                AttributeEntity att = new AttributeEntity(attdef);
×
415

UNCOV
416
                                this.Attributes.Add(att);
×
UNCOV
417
                        }
×
UNCOV
418
                }
×
UNCOV
419
        }
×
420

421
        internal override void AssignDocument(CadDocument doc)
422
        {
8,349✔
423
                base.AssignDocument(doc);
8,349✔
424

425
                doc.RegisterCollection(this.Attributes);
8,349✔
426

427
                //Should only be triggered for internal use
428
                if (this.Block == null)
8,349✔
429
                        return;
7,880✔
430

431
                if (doc.BlockRecords.TryGetValue(this.Block.Name, out BlockRecord blk))
469✔
432
                {
171✔
433
                        this.Block = blk;
171✔
434
                }
171✔
435
                else
436
                {
298✔
437
                        doc.BlockRecords.Add(this.Block);
298✔
438
                }
298✔
439
        }
8,349✔
440

441
        internal override void UnassignDocument()
442
        {
205✔
443
                this.Block = (BlockRecord)this.Block.Clone();
205✔
444
                this.Document.UnregisterCollection(this.Attributes);
205✔
445

446
                base.UnassignDocument();
205✔
447
        }
205✔
448

449
        private void initCollections()
450
        {
8,355✔
451
                this.Attributes = new SeqendCollection<AttributeEntity>(this);
8,355✔
452
                this.Attributes.OnAdd += this.onAddAttribute;
8,355✔
453
        }
8,355✔
454

455
        private void onAddAttribute(object sender, CollectionChangedEventArgs e)
456
        {
1,387✔
457
                Transform transform = this.GetTransform();
1,387✔
458
                (e.Item as AttributeEntity).ApplyTransform(transform);
1,387✔
459
        }
1,387✔
460
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc