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

DomCR / ACadSharp / 27237908933

09 Jun 2026 09:46PM UTC coverage: 77.001%. Remained the same
27237908933

Pull #1111

github

web-flow
Merge e90fc207a into 5027f0b2b
Pull Request #1111: Fix insert bounding boxes for rotated blocks

8623 of 12155 branches covered (70.94%)

Branch coverage included in aggregate %.

14 of 16 new or added lines in 1 file covered. (87.5%)

11 existing lines in 2 files now uncovered.

31135 of 39478 relevant lines covered (78.87%)

157316.49 hits per line

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

59.8
/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; }
20,738✔
30

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

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

43
        /// <summary>
44
        /// Column spacing
45
        /// </summary>
46
        [DxfCodeValue(DxfReferenceType.Optional, 44)]
47
        public double ColumnSpacing { get; set; } = 0;
9,145✔
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,410✔
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;
49,330✔
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,281✔
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;
11,598✔
75

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

79
        /// <inheritdoc/>
80
        public override ObjectType ObjectType
81
        {
82
                get
83
                {
96✔
84
                        if (this.RowCount > 1 || this.ColumnCount > 1)
96✔
85
                        {
16✔
86
                                return ObjectType.MINSERT;
16✔
87
                        }
88
                        else
89
                        {
80✔
90
                                return ObjectType.INSERT;
80✔
91
                        }
92
                }
96✔
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,314✔
103

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

110
        /// <summary>
111
        /// Row spacing
112
        /// </summary>
113
        [DxfCodeValue(DxfReferenceType.Optional, 45)]
114
        public double RowSpacing { get; set; } = 0;
9,145✔
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;
328!
151

152
        /// <summary>
153
        /// X scale factor.
154
        /// </summary>
155
        [DxfCodeValue(41)]
156
        public double XScale
157
        {
158
                get
159
                {
535✔
160
                        return this._xscale;
535✔
161
                }
535✔
162
                set
163
                {
5,340✔
164
                        if (value.Equals(0))
5,340✔
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;
5,339✔
170
                }
5,339✔
171
        }
172

173
        /// <summary>
174
        /// Y scale factor.
175
        /// </summary>
176
        [DxfCodeValue(42)]
177
        public double YScale
178
        {
179
                get
180
                {
449✔
181
                        return this._yscale;
449✔
182
                }
449✔
183
                set
184
                {
5,123✔
185
                        if (value.Equals(0))
5,123✔
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;
5,122✔
191
                }
5,122✔
192
        }
193

194
        /// <summary>
195
        /// Z scale factor.
196
        /// </summary>
197
        [DxfCodeValue(43)]
198
        public double ZScale
199
        {
200
                get
201
                {
443✔
202
                        return this._zscale;
443✔
203
                }
443✔
204
                set
205
                {
5,101✔
206
                        if (value.Equals(0))
5,101✔
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;
5,100✔
212
                }
5,100✔
213
        }
214

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

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

219
        private double _zscale = 1;
8,779✔
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()
110✔
227
        {
110✔
228
                if (block is null) throw new ArgumentNullException(nameof(block));
110!
229

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

239
                foreach (var item in block.AttributeDefinitions)
424✔
240
                {
47✔
241
                        var att = new AttributeEntity(item);
47✔
242

243
                        Transform transform = this.GetTransform();
47✔
244
                        att.ApplyTransform(transform);
47✔
245

246
                        this.Attributes.Add(att);
47✔
247
                }
47✔
248
        }
110✔
249

250
        internal Insert() : base()
8,779✔
251
        {
8,779✔
252
                this.initCollections();
8,779✔
253
        }
8,779✔
254

255
        /// <inheritdoc/>
256
        public override void ApplyTransform(Transform transform)
257
        {
×
258
                XYZ newPosition = transform.ApplyTransform(this.InsertPoint);
×
259
                XYZ newNormal = this.transformNormal(transform, this.Normal);
×
260

261
                Matrix3 transOW = Matrix3.ArbitraryAxis(this.Normal);
×
262
                transOW *= Matrix3.RotationZ(this.Rotation);
×
263

264
                Matrix3 transWO = Matrix3.ArbitraryAxis(newNormal);
×
265
                transWO = transWO.Transpose();
×
266

267
                var transformation = new Matrix3(transform.Matrix);
×
268
                XYZ v = transOW * XYZ.AxisX;
×
269
                v = transformation * v;
×
270
                v = transWO * v;
×
271
                double newRotation = new XY(v.X, v.Y).GetAngle();
×
272

273
                transWO = Matrix3.RotationZ(newRotation).Transpose() * transWO;
×
274

275
                XYZ s = transOW * new XYZ(this.XScale, this.YScale, this.ZScale);
×
276
                s = transformation * s;
×
277
                s = transWO * s;
×
278
                XYZ newScale = new XYZ(
×
279
                        MathHelper.IsZero(s.X) ? MathHelper.Epsilon : s.X,
×
280
                        MathHelper.IsZero(s.Y) ? MathHelper.Epsilon : s.Y,
×
281
                        MathHelper.IsZero(s.Z) ? MathHelper.Epsilon : s.Z);
×
282

283
                this.Normal = newNormal;
×
284
                this.InsertPoint = newPosition;
×
285
                this.XScale = newScale.X;
×
286
                this.YScale = newScale.Y;
×
287
                this.ZScale = newScale.Z;
×
288
                this.Rotation = newRotation;
×
289

290
                foreach (AttributeEntity att in this.Attributes)
×
291
                {
×
292
                        att.ApplyTransform(transform);
×
293
                }
×
294
        }
×
295

296
        /// <inheritdoc/>
297
        public override CadObject Clone()
298
        {
181✔
299
                Insert clone = (Insert)base.Clone();
181✔
300

301
                clone.Block = (BlockRecord)this.Block?.Clone();
181✔
302

303
                clone.initCollections();
181✔
304
                foreach (var att in this.Attributes)
545✔
305
                {
1✔
306
                        clone.Attributes.Add((AttributeEntity)att.Clone());
1✔
307
                }
1✔
308

309
                return clone;
181✔
310
        }
181✔
311

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

332
                                        Arc a = new Arc(
×
333
                                                        transform.ApplyTransform(arc.Center),
×
334
                                                        transform.ApplyTransform(start),
×
335
                                                        transform.ApplyTransform(end),
×
336
                                                        arc.Normal);
×
337

338
                                        a.MatchProperties(e);
×
339

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

357
                        c.ApplyTransform(transform);
×
358

359
                        yield return c;
×
360
                }
×
361
        }
×
362

363
        /// <inheritdoc/>
364
        public override BoundingBox GetBoundingBox()
365
        {
6✔
366
                BoundingBox box = this.Block.GetBoundingBox();
6✔
367
                if (box.Extent == BoundingBoxExtent.Null || box.Extent == BoundingBoxExtent.Infinite)
6!
NEW
368
                {
×
NEW
369
                        return box;
×
370
                }
371

372
                var t = this.GetTransform();
6✔
373

374
                var points = new[]
6✔
375
                {
6✔
376
                        new XYZ(box.Min.X, box.Min.Y, box.Min.Z),
6✔
377
                        new XYZ(box.Min.X, box.Min.Y, box.Max.Z),
6✔
378
                        new XYZ(box.Min.X, box.Max.Y, box.Min.Z),
6✔
379
                        new XYZ(box.Min.X, box.Max.Y, box.Max.Z),
6✔
380
                        new XYZ(box.Max.X, box.Min.Y, box.Min.Z),
6✔
381
                        new XYZ(box.Max.X, box.Min.Y, box.Max.Z),
6✔
382
                        new XYZ(box.Max.X, box.Max.Y, box.Min.Z),
6✔
383
                        new XYZ(box.Max.X, box.Max.Y, box.Max.Z),
6✔
384
                }
6✔
385
                .Select(point => t.ApplyTransform(point));
54✔
386

387
                return BoundingBox.FromPoints(points);
6✔
388
        }
6✔
389

390
        /// <summary>
391
        /// Get the transform that will be applied to the entities in the <see cref="BlockRecord"/> when this entity is processed.
392
        /// </summary>
393
        /// <returns></returns>
394
        public Transform GetTransform()
395
        {
54✔
396
                var world = Matrix4.GetArbitraryAxis(this.Normal);
54✔
397
                var translation = Transform.CreateTranslation(this.InsertPoint);
54✔
398
                var rotation = Transform.CreateRotation(XYZ.AxisZ, this.Rotation);
54✔
399
                var scale = Transform.CreateScaling(new XYZ(this.XScale, this.YScale, this.ZScale));
54✔
400

401
                return new Transform(world * translation.Matrix * rotation.Matrix * scale.Matrix);
54✔
402
        }
54✔
403

404
        /// <summary>
405
        /// Updates all attribute definitions contained in the block reference as <see cref="AttributeDefinition"/> entities in the insert.
406
        /// </summary>
407
        /// <remarks>
408
        /// This will update the attributes based on their <see cref="AttributeBase.Tag"/>.
409
        /// </remarks>
410
        public void UpdateAttributes()
411
        {
×
412
                var atts = this.Attributes.ToArray();
×
413

414
                foreach (AttributeEntity att in atts)
×
415
                {
×
416
                        //Tags are not unique, is it needed? check how the different applications link the atts
417
                        if (!this.Block.AttributeDefinitions.Select(d => d.Tag).Contains(att.Tag))
×
418
                        {
×
419
                                this.Attributes.Remove(att);
×
420
                        }
×
421
                }
×
422

423
                foreach (AttributeDefinition attdef in this.Block.AttributeDefinitions)
×
424
                {
×
425
                        if (!this.Attributes.Select(d => d.Tag).Contains(attdef.Tag))
×
426
                        {
×
427
                                AttributeEntity att = new AttributeEntity(attdef);
×
428

429
                                Transform transform = this.GetTransform();
×
430
                                att.ApplyTransform(transform);
×
431

432
                                this.Attributes.Add(att);
×
433
                        }
×
434
                }
×
435
        }
×
436

437
        internal override void AssignDocument(CadDocument doc)
438
        {
8,953✔
439
                base.AssignDocument(doc);
8,953✔
440

441
                doc.RegisterCollection(this.Attributes);
8,953✔
442

443
                //Should only be triggered for internal use
444
                if (this.Block == null)
8,953✔
445
                        return;
8,473✔
446

447
                if (doc.BlockRecords.TryGetValue(this.Block.Name, out BlockRecord blk))
480✔
448
                {
182✔
449
                        this.Block = blk;
182✔
450
                }
182✔
451
                else
452
                {
298✔
453
                        doc.BlockRecords.Add(this.Block);
298✔
454
                }
298✔
455
        }
8,953✔
456

457
        internal override void UnassignDocument()
458
        {
205✔
459
                this.Block = (BlockRecord)this.Block.Clone();
205✔
460
                this.Document.UnregisterCollection(this.Attributes);
205✔
461

462
                base.UnassignDocument();
205✔
463
        }
205✔
464

465
        private void initCollections()
466
        {
8,960✔
467
                this.Attributes = new SeqendCollection<AttributeEntity>(this);
8,960✔
468
        }
8,960✔
469
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc